Custom init for UIViewController in Swift with interface setup in storyboard

73,001

Solution 1

As it was specified in one of the answers above you can not use both and custom init method and storyboard.

But you still can use a static method to instantiate ViewController from a storyboard and perform additional setup on it.

It will look like this:

class MemeDetailVC : UIViewController {
    
    var meme : Meme!
    
    static func makeMemeDetailVC(meme: Meme) -> MemeDetailVC {
        let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "IdentifierOfYouViewController") as! MemeDetailVC
        
        newViewController.meme = meme
        
        return newViewController
    }
}

Don't forget to specify IdentifierOfYouViewController as view controller identifier in your storyboard. You may also need to change the name of the storyboard in the code above.

Solution 2

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).

Solution 3

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())

Solution 4

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.

Solution 5

There were originally a couple of answers, which were cow voted and deleted even though they were basically correct. The answer is, you can't.

When working from a storyboard definition your view controller instances are all archived. So, to init them it's required that init?(coder... be used. The coder is where all the settings / view information comes from.

So, in this case, it's not possible to also call some other init function with a custom parameter. It should either be set as a property when preparing the segue, or you could ditch segues and load the instances directly from the storyboard and configure them (basically a factory pattern using a storyboard).

In all cases you use the SDK required init function and pass additional parameters afterwards.

Share:
73,001
Pigfly
Author by

Pigfly

Updated on December 04, 2021

Comments

  • Pigfly
    Pigfly over 2 years

    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 setting property directly like viewControllerB.property = value

    So I made a custom init for my viewController and call super designated init

    init(meme: Meme?) {
            self.meme = meme
            super.init(nibName: nil, bundle: nil)
        }
    

    The view controller interface resides in storyboard, I've also make the interface for custom class to be my view controller. And Swift requires to call this init method even if you are not doing anything within this method. Otherwise the compiler will complain...

    required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    

    The problem is when I try to call my custom init with MyViewController(meme: meme) it doesn't init properties in my viewController at all...

    I was trying to debug, I found in my viewController, init(coder aDecoder: NSCoder) get called first, then my custom init get called later. However these two init method return different self memory addresses.

    I'm suspecting something wrong with the init for my viewController, and it will always return self with the init?(coder aDecoder: NSCoder), which, has no implementation.

    Does anyone know how to make custom init for your viewController correctly ? Note: my viewController's interface is set up in storyboard

    here is my viewController code:

    class MemeDetailVC : UIViewController {
    
        var meme : Meme!
    
        @IBOutlet weak var editedImage: UIImageView!
    
        // TODO: incorrect init
        init(meme: Meme?) {
            self.meme = meme
            super.init(nibName: nil, bundle: nil)
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        override func viewDidLoad() {
            /// setup nav title
            title = "Detail Meme"
    
            super.viewDidLoad()
        }
    
        override func viewWillAppear(animated: Bool) {
            super.viewWillAppear(animated)
            editedImage = UIImageView(image: meme.editedImage)
        }
    
    }