1

I want to make a new kind of view controller in Swift that has an additional property that must be explicitly initialized. It doesn't make any sense for this property to be nil or have a default value and the controller will only be initialized programmatically. I tried defining it like this:

class MyController : UIViewController {
  var prop: Int
  init(prop: Int) {
    self.prop = prop
    super.init()
  }

  required init(coder aDecoder: NSCoder?) {
    fatalError("don't serialize this")
  }
}

I tried running this but it crashed because super.init() tries to run the nib constructor which isn't defined, so I tried adding that:

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
  super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

But now the compiler complains that prop isn't being initialized. And this is my question: how can I initialize prop correctly here? I don't want a default value, and anything I set will override the correct value that I set in the other initializer.

I kinda hacked around it by setting some default value in the nib init, but then having my first init do this

self.prop = prop
super.init()
self.prop = prop

But other than being really weird and ugly, that makes me worried that now it is possible to initialize my view controller from a nib and end up with the default value, which would be bad.

What is the correct and idiomatic way to do this in Swift?

mfaani
  • 33,269
  • 19
  • 164
  • 293
Max
  • 21,123
  • 5
  • 49
  • 71
  • How is the view controller to be created? Storyboard? Programmatically? – picciano May 17 '18 at 13:58
  • @picciano programmatically – Max May 17 '18 at 13:59
  • Have you thought about a protocol instead of inheritance? It's much cleaner and seems to currently be the convention for adding properties to existing classes. – kevin May 17 '18 at 14:18
  • Alternatively, could the view controller pull in the data it needs rather than having it be set in the init function? – picciano May 17 '18 at 14:29
  • Initializers are good for programmatically created viewControllers, but for viewcontrollers created through storyboard [you're out of luck and have to work your way around it](https://stackoverflow.com/a/39400793/5175709) – mfaani Aug 27 '19 at 20:30

2 Answers2

5

At some point the view controller must be initialized by calling init(nibName:bundle:) or init(coder:)

Try this:

class MyViewController: UIViewController {

    var prop: Int

    init(prop: Int, nibName nibNameOrNil: String? = nil, bundle nibBundleOrNil: Bundle? = nil) {
        self.prop = prop
        super.init(nibName:nibNameOrNil, bundle: nibBundleOrNil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
picciano
  • 22,341
  • 9
  • 69
  • 82
  • Crap, the problem is that my view controller doesn't subclass UIViewController directly, there's an intermediate that needs to run. I left it out of the question initially because I didn't think it mattered... – Max May 17 '18 at 14:16
  • Might need to see that as well then. You should be able to just substitute your subclass for the `UIViewController`, but generally I like to discourage subclassing in favor of Protocols. Most functionality you want to accomplish with a "base" view controller can really be done with Protocols. But that may be another question to post... – picciano May 17 '18 at 14:20
  • Actually, this did help me figure it out! The problem is that the superclass was calling `[super init]` rather than `[super initWithNibName:nil bundle:nil]`. The first is just a convenience method for the second, but because of polymorphism it ended up calling the nib init in `MyViewController`, which I didn't want! Changing it the explicit second form ensures that this doesn't happen. I think I actually demonstrate this in my question by calling `super.init()`, same problem. – Max May 17 '18 at 14:30
1

Try the following

class MyController: UIViewController {

    var prop: Int

    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }

    init(prop: Int) {
        self.prop = prop
        super.init(nibName: nil, bundle: nil)
    }
}
JaredH
  • 2,338
  • 1
  • 30
  • 40
  • Doesn't work for me using Swift 4, UIViewController does not have `init(frame:)` and must call designated init method. I think you are thinking of UIView – Scriptable May 17 '18 at 14:04
  • But then I need to define init(frame:)? – Max May 17 '18 at 14:04