I\'m having issue for writing custom init for subclass of UIViewController, basically I want to pass the dependency through the init method for viewController rather than se
One way that I've done this is with a convenience initializer.
class MemeDetailVC : UIViewController {
convenience init(meme: Meme) {
self.init()
self.meme = meme
}
}
Then you initialize your MemeDetailVC with let memeDetailVC = MemeDetailVC(theMeme)
Apple's documentation on initializers is pretty good, but my personal favorite is the Ray Wenderlich: Initialization in Depth tutorial series which should give you plenty of explanation/examples on your various init options and the "proper" way to do things.
EDIT: While you can use a convenience initializer on custom view controllers, everyone is correct in stating that you cannot use custom initializers when initializing from the storyboard or through a storyboard segue.
If your interface is set up in the storyboard and you're creating the controller completely programmatically, then a convenience initializer is probably the easiest way to do what you're trying to do since you don't have to deal with the required init with the NSCoder (which I still don't really understand).
If you're getting your view controller via the storyboard though, then you will need to follow @Caleb Kleveter's answer and cast the view controller into your desired subclass then set the property manually.
As of iOS 13 you can initialize the view controller that resides in a storyboard using:
instantiateViewController(identifier:creator:)
method on the UIStoryboard
instance.
tutorial: https://sarunw.com/posts/better-dependency-injection-for-storyboards-in-ios13/
Correct flow is, call the designated initializer which in this case is the init with nibName,
init(tap: UITapGestureRecognizer)
{
// Initialise the variables here
// Call the designated init of ViewController
super.init(nibName: nil, bundle: nil)
// Call your Viewcontroller custom methods here
}
Swift 5
You can write custom initializer like this ->
class MyFooClass: UIViewController {
var foo: Foo?
init(with foo: Foo) {
self.foo = foo
super.init(nibName: nil, bundle: nil)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.foo = nil
}
}
As @Caleb Kleveter has pointed out, we can't use a custom initializer while initialising from a Storyboard.
But, we can solve the problem by using factory/class method which instantiate view controller object from Storyboard and return view controller object. I think this is a pretty cool way.
Note: This is not an exact answer to question rather a workaround to solve the problem.
Make class method, in MemeDetailVC class, as follows:
// Considering your view controller resides in Main.storyboard and it's identifier is set to "MemeDetailVC"
class func `init`(meme: Meme) -> MemeDetailVC? {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "MemeDetailVC") as? MemeDetailVC
vc?.meme = meme
return vc
}
Usage:
let memeDetailVC = MemeDetailVC.init(meme: Meme())
You can't use a custom initializer when you initialize from a Storyboard, using init?(coder aDecoder: NSCoder)
is how Apple designed the storyboard to initialize a controller. However, there are ways to send data to a UIViewController
.
Your view controller's name has detail
in it, so I suppose that you get there from a different controller. In this case you can use the prepareForSegue
method to send data to the detail (This is Swift 3):
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "identifier" {
if let controller = segue.destinationViewController as? MemeDetailVC {
controller.meme = "Meme"
}
}
}
I just used a property of type String
instead of Meme
for testing purposes. Also, make sure that you pass in the correct segue identifier ("identifier"
was just a placeholder).