How do I initialize UIView with strings in Swift 2? - swift

I have a UIView that plays videos. I have successfully played the video inside the view, however, I would like to pass in two string values in the view.
class CardView: UIView {
private var bucketname: String!
private var objectname: String!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
convenience init(bucket: String, object: String){
self.init(frame: CGRectZero)
self.bucketname = bucket
self.objectname = object
initialize()
}
private func initialize() {
print(self.bucketname)
print(self.objectname)
}
}
I guess it is returning nil because the self.init is called before I pass in the values, but I cant assign the values without calling self.init which calls initialize() without the values that I want to pass in. How do I get around this ?

Related

Swift UIView subclass init failing

I created a UIView subclass:
class RA_Circle: UIView {
let elipseWidth:CGFloat
let elipseHeight:CGFloat
init(elipseWidth: CGFloat, elipseHeight:CGFloat) {
self.elipseWidth = elipseWidth
self.elipseHeight = elipseHeight
super.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
But the compiler is giving me this error on the required init:
Property 'self.elipseWidth' not initialized at super.init call
I was following this SO Q&A:
How to properly init passed property in subclass of UIView?
In your class definition you're declaring your variable without any default values, and the required initializer is not providing any values before calling super init.
There are a number of ways to deal with this, here's a few...
One way is to provide default values in the property declarations:
var elipseWidth: CGFloat = 0.0
var elipseHeight: CGFloat = 0.0
The second is to make your properties optional:
var elipseWidth: CGFloat?
var elipseHeight: CGFloat?
A third is to provide default values in the required initializer:
required init?(coder aDecoder: NSCoder) {
elipseWidth = 14.0
elipseHeight = 14.0
super.init(coder: aDecoder)
}
You have to change the let variable declarations to var and optional. In this case, you would initialize them in awakeFromNib() or at some later time.

How can I subclass UISegmentedControl having custom designated initializer?

Seems like a trivial issue but I am not able to make this compile.
Neither in playgrounds nor in normal swift ios projects.
(Please note I am not using Storyboards that's why I don't need / care about the init?(coder) part..it;s jsut it has to be include otherwise the complier complains about it.)
class SegmentedControl: UISegmentedControl {
let configuration: [String]
required init(configuration: [String]) {
self.configuration = configuration
super.init(items: configuration)
}
required init?(coder aDecoder: NSCoder) { fatalError() }
}
let x = SegmentedControl(configuration: ["a","b"])
It is complaining about not having the deisignated initializer implemented.
Fatal error: Use of unimplemented initializer 'init(frame:)' for class
'__lldb_expr_167.SegmentedControl'
I don't understand what is going on here. Isn't the designated initializer the init(items:) for UISegmentedControl? I am calling it in my subclass designated initializer.
Solution:
class SegmentedControl: UISegmentedControl {
var configuration = [String]()
required init(configuration: [String]) {
super.init(items: configuration)
self.configuration = configuration
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) { fatalError() }
}
let x = SegmentedControl(configuration: ["a","b"])

Swift: How to add new property to UIImageView class?

I'm afraid I'm relatively new to Swift, but have looked around as best I could and haven't been able to figure out how to do this relatively simple task!
I would like to add a new property called "angle" to the class UIImageView, such that you could use "image.angle". Here's what I've got, having attempted to follow the method of a tutorial I used (note that the required init? part was suggested by Xcode and I am not too sure of what it does):
class selection_image: UIImageView {
var angle = Double()
init(angle: Double) {
self.angle = angle
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Thank you very much for any help!!
class selection_image: UIImageView {
var angle = Double()
// your new Init
required convenience init(angle: Double) {
self.init(frame: CGRect.zero)
self.angle = angle
}
override init(frame: CGRect) {
super.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Using Swift 4.2
#jasperthedog. You can add a property to a given class using AssociatedObjects of the Runtime using an extension as follows:
In this example, I add an optional viewAlreadyAppeared: Bool? property to UIViewController. And with this, I avoid creating subclasses of UIViewController
extension UIViewController {
private struct CustomProperties {
static var viewAlreadyAppeared: Bool? = nil
}
var viewAlreadyAppeared: Bool? {
get {
return objc_getAssociatedObject(self, &CustomProperties.viewAlreadyAppeared) as? Bool
}
set {
if let unwrappedValue = newValue {
objc_setAssociatedObject(self, &CustomProperties.viewAlreadyAppeared, unwrappedValue as Bool?, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}

How do you get the frame.width of GameScene in a custom class?

I'm having trouble trying to get the frame.width of GameScene into a custom class because I need the scene width for positioning nodes in my custom class init.
HudController.swift snippet
import SpriteKit
class HudController:SKNode {
var width:CGFloat
override init(){
width = GameScene().frame.width //I was hoping to add the scene width here
}
}
The following code crashes on me and I've tried heaps of other solutions with no success.
Can someone please help me with this issue?
Thanks!
UPDATED CODE ===
GameScene.swift
import SpriteKit
class GameScene: SKScene {
var hud = HudController(gameScene: self)
// set as global because I'm using it to also call hud functions in my game
}
HudController.swift
import SpriteKit
class HudController:SKNode {
var width:CGFloat
init(gameScene:SKScene){
width = gameScene.size.width
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Every node in SpriteKit can retrieve the scene where it lives using the scene property.
let node = SKNode()
node.scene
The scene property returns a SKScene?, the value is an optional because if the current node does not belong to a scene, of course there is not scene to retrieve.
Now, you could be tempted to use the self.scene property inside the initializer of your HudController.
class HudController: SKNode {
var width:CGFloat
override init() {
super.init() // error: property 'self.width' not initialized at super.init call
width = self.scene!.frame.width
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
But it will now work because you cannot invoke super.init before having initialized the width property.
Fine, then you could try this.
class HudController: SKNode {
var width:CGFloat
override init() {
width = self.scene!.frame.width // error: use of 'self' in property access 'scene' before super.init initializes self
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Again compiler error because you are using self before having called super.init().
And finally there is another problem, when the initializer is executed, the node it is being created has not a parent yet, so the self.scene property does return nil.
How to solve this?
Solution
First of all declare you initializer to receive a GameScene.
class HudController: SKNode {
var width: CGFloat
init(gameScene:SKScene) {
width = gameScene.frame.width
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now, when you create this object (I hope you are doing it from the scene or from a node already added to the scene otherwise we are in trouble) you simply write
class Foo : SKNode {
func foo() {
guard let scene = self.scene else { return }
let controller = HudController(gameScene: scene)
}
}
On the other hand if you are creating HudController from you scene you simply write:
class MyScene : SKScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
let controller = HudController(gameScene: self)
}
}
That's it.
Update
If you want an instance property of HudController inside your GameScene this is the code.
class HudController: SKNode {
var width: CGFloat
init(gameScene:SKScene) {
width = gameScene.frame.width
super.init() // <-- do not forget this
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameScene: SKScene {
var hud : HudController?
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
hud = HudController(gameScene: self)
}
}

How do I make a custom initializer for a UIViewController subclass in Swift?

Apologies if this has been asked before, I've searched around a lot and many answers are from earlier Swift betas when things were different. I can't seem to find a definitive answer.
I want to subclass UIViewController and have a custom initializer to allow me to set it up in code easily. I'm having trouble doing this in Swift.
I want an init() function that I can use to pass a specific NSURL I'll then use with the view controller. In my mind it looks something like init(withImageURL: NSURL). If I add that function it then asks me to add the init(coder: NSCoder) function.
I believe this is because it's marked in the superclass with the required keyword? So I have to do it in the subclass? I add it:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Now what? Is my special initializer considered a convenience one? A designated one? Do I call a super initializer? An initializer from the same class?
How do I add my special initializer onto a UIViewController subclass?
class ViewController: UIViewController {
var imageURL: NSURL?
// this is a convenient way to create this view controller without a imageURL
convenience init() {
self.init(imageURL: nil)
}
init(imageURL: NSURL?) {
self.imageURL = imageURL
super.init(nibName: nil, bundle: nil)
}
// if this view controller is loaded from a storyboard, imageURL will be nil
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
For those who write UI in code
class Your_ViewController : UIViewController {
let your_property : String
init(your_property: String) {
self.your_property = your_property
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported")
}
}
This is very similar to the other answers, but with some explanation. The accepted answer is misleading because its property is optional and doesn't expose the fact that your init?(coder: NSCoder) MUST initialize each and every property and the only solution to that is having a fatalError(). Ultimately you could get away by making your properties optionals, but that doesn't truly answer the OP’s question.
// Think more of a OnlyNibOrProgrammatic_NOTStoryboardViewController
class ViewController: UIViewController {
let name: String
override func viewDidLoad() {
super.viewDidLoad()
}
// I don't have a nib. It's all through my code.
init(name: String) {
self.name = name
super.init(nibName: nil, bundle: nil)
}
// I have a nib. I'd like to use my nib and also initialze the `name` property
init(name: String, nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle? ) {
self.name = name
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
// when you do storyboard.instantiateViewController(withIdentifier: "ViewController")
// The SYSTEM will never call this!
// it wants to call the required initializer!
init?(name: String, coder aDecoder: NSCoder) {
self.name = "name"
super.init(coder: aDecoder)
}
// when you do storyboard.instantiateViewController(withIdentifier: "ViewController")
// The SYSTEM WILL call this!
// because this is its required initializer!
// but what are you going to do for your `name` property?!
// are you just going to do `self.name = "default Name" just to make it compile?!
// Since you can't do anything then it's just best to leave it as `fatalError()`
required init?(coder aDecoder: NSCoder) {
fatalError("I WILL NEVER instantiate through storyboard! It's impossible to initialize super.init?(coder aDecoder: NSCoder) with any other parameter")
}
}
You basically have to ABANDON loading it from storyboard. Why?
Because when you call a viewController storyboard.instantiateViewController(withIdentifier: "viewController") then UIKit will do its thing and call
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
You can never redirect that call to another init method.
Docs on instantiateViewController(withIdentifier:):
Use this method to create a view controller object to present
programmatically. Each time you call this method, it creates a new
instance of the view controller using the init(coder:) method.
Yet for programmatically created viewController or nib created viewControllers you can redirect that call as shown above.
Convenience initializers are secondary, supporting initializers for a
class. You can define a convenience initializer to call a designated
initializer from the same class as the convenience initializer with
some of the designated initializer’s parameters set to default values.
You can also define a convenience initializer to create an instance of
that class for a specific use case or input value type.
They are documented here.
If you need a custom init for a popover for example you can use the following approach:
Create a custom init that uses the super init with nibName and bundle and after that access the view property to force the load of the view hierarchy.
Then in the viewDidLoad function you can configure the views with the parameters passed in the initialization.
import UIKit
struct Player {
let name: String
let age: Int
}
class VC: UIViewController {
#IBOutlet weak var playerName: UILabel!
let player: Player
init(player: Player) {
self.player = player
super.init(nibName: "VC", bundle: Bundle.main)
if let view = view, view.isHidden {}
}
override func viewDidLoad() {
super.viewDidLoad()
configure()
}
func configure() {
playerName.text = player.name + "\(player.age)"
}
}
func showPlayerVC() {
let foo = Player(name: "bar", age: 666)
let vc = VC(player: foo)
present(vc, animated: true, completion: nil)
}