Lately I’ve been struggling to balance the pros and cons of various view controller initialization techniques. There are three, none of which are perfect:
Doing it All In Code - PRO: I have complete control over when and how my dependencies and views get initialized. CON: Some complex view hierarchies are easier to manage using the Interface Builder GUI (primarily conditional edits for different size class combinations).
NIBs - PRO: I’m able to pass in dependencies to parent view controllers (Core Data stack access, delegates, API clients, etc) at initialization time. Since I work in Swift, this helps avoid inconvenient optionals or dangerous force-unwrapped optionals. CON: NIBs lack some useful features that Storyboards have, like layout guides or automatic cell nib registration.
Storyboards - PRO: All the GUI benefits of NIBs, with addtional perks like top/bottom layout guides and automatic cell nib registration. CON: Storyboards are prone to a whole host of other shortcomings which I won’t list here (a few of which are also shared by NIBs). The most problematic shorticoming is that, unlike NIBs, UIViewController has no equivalent
initWithStoryboard()method. Why does the lack of an
initWithStoryboard()method matter? It means you cannot provide a custom init method for your class, which means you cannot pass in dependencies at initialization time. This is true even if you don’t use segues and only use one view controller per storyboard. This implies that the view controller’s dependencies are public read/write properties. It’s an antipattern for parents to have that much introspective knowledge about how to configure their children. Public read/write dependencies also expose a surface area for bugs from API misuse. Exposing an otherwise
private letdependency as a public
varimplies that the property is free to use.
Lately, the projects I’ve been working on have had pretty heavy AutoLayout requirements. As such, it’s been easiest to build the user interface logic using a Storyboard. But the Storyboard approach makes it really hard to pass in dependencies in a simple, reliable fashion.
Until Apple provides a better solution, my current strategy is to provide a static factory method that takes as arguments all the dependencies that need to be passed in:
static func newFromStoryboard(foo: FooDependency, bar: BarDependency) -> MyViewController
In addition, I make the relevant instance variables private, so that they can only be set from inside the file:
private var foo: FooDependency! private var bar: BarDependency!
The factory method instantiates the view controller from the correct storyboard, and then sets all the dependencies. I only use one view controller per storyboard, and never use segues.
This is not the most elegant approach I can imagine, but I believe it’s the least inelegant approach made possible within the current state of the UIKit framework and Swift.