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

82,403

Solution 1

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

Solution 2

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")
    }
}

Solution 3

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.

Solution 4

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.

Share:
82,403

Related videos on Youtube

Doug Smith
Author by

Doug Smith

I'm a web designer playing around with iOS from England

Updated on August 16, 2021

Comments

  • Doug Smith
    Doug Smith over 2 years

    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?

  • Van Du Tran
    Van Du Tran about 9 years
    can imageURL be a constant instead? ("let imageURL: NSURL?)
  • linimin
    linimin about 9 years
    Sure @VanDuTran, we can initiailze a constant in initiailzers.
  • siege_Perilous
    siege_Perilous almost 9 years
    You don't need to call any designated initializers in your new default initializer?
  • Chris Hayes
    Chris Hayes about 8 years
    not working xCode 7, the required constructor is failing to initialize the class level property, in my case an Int, not NSURL?
  • Nikola Lukic
    Nikola Lukic over 7 years
    How to load this kind of viewController ?
  • linimin
    linimin over 7 years
    @NikolaLukic - We can creates a new instance of the class by calling ViewController(), ViewController(imageURL: url), or loading it from a storyboard.
  • Nikola Lukic
    Nikola Lukic over 7 years
    Now i get it , i can make manipulation with class instance before i put it in the present view ! Nice...
  • Sujay U N
    Sujay U N about 7 years
    I get this error Convenience initializer for 'NamCls' must delegate (with 'self.init') rather than chaining to a superclass initializer (with 'super.init')
  • linimin
    linimin about 7 years
    @SujayUN - A convenience initializer can only call another initializer from the same class.
  • mfaani
    mfaani over 6 years
    I had to write mine as required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } otherwise with super.init(coder: aDecoder) I was getting the error of Property self.someProperty not initialized at super.init call
  • linimin
    linimin over 6 years
    @Honey - All stored property must be assigned an initial value before calling super.init. In the sample code above, imageURL is an optional variable, so it's nil by default.
  • mfaani
    mfaani over 6 years
    Not sure how that's related. If you're doing pure code, then you don't need to populate your properties inside the init(coder: aDecoder). The fatalError will suffice
  • linimin
    linimin over 6 years
    @Honey - Yes, that's totally fine.
  • mfaani
    mfaani over 4 years
    @others This answer is slightly misleading. I put a different answer here which addresses the comments I made before.
  • Peter Kovacs
    Peter Kovacs over 2 years
    This isn't strictly true any more, you can "redirect" the call to another init method by using the instantiateViewController(identifier:creator:) method that's available in iOS 13+