iOS: View Controller Data Injection with Storyboards and Segues in Swift
Since I openly declared my love for Storyboards & Nibs earlier this week, I’ve gotten a lot of questions on how I deal with injecting dependencies between View Controllers without using a custom initializer. So I’ll share that now.
But first, I’d like to say that my solution is not perfect, and it’s a workaround around how UIViewController works. Hopefully Apple will provide a more flexible and Swifty UIKit API (with protocols instead of subclassing!) down the line that will allow the very common case of dependency injection!
Second, the reason I’m a against messing with the initializers is because we have to subclass our UIViewControllers from UIKit. There are already several initializers there and we have to call super on them because UIKit is doing work behind the scenes (which we cannot see the source code of):
1 2 3 4 5 6 7 8 9 10 11 12 |
// comments from Apple's UIViewController code /* The designated initializer. If you subclass UIViewController, you must call the super implementation of this method, even if you aren't using a NIB. (As a convenience, the default init method will do this for you, and specify nil for both of this methods arguments.) In the specified NIB, the File's Owner proxy should have its class set to your view controller subclass, with the view outlet connected to the main view. If you invoke this method with a nil nib name, then this class' -loadView method will attempt to load a NIB whose name is the same as your view controller's class. If no such NIB in fact exists then you must either call -setView: before -view is invoked, or override the -loadView method to set up your views programatically. */ public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) public init?(coder aDecoder: NSCoder) |
Sure, it’s been the same for while and will likely not change, but since I don’t have control or visibility into UIKit, I prefer to work with how it’s meant to be used rather than against it by creating my own clever initializers!
So the way I prefer to handle this issue is by using Implicitly Unwrapped Optionals 😱😱😱
I’ll use this project as an example. The detail view controller – the one that depends on a value from the previous view controller, has an implicitly unwrapped optional variable for the value that it absolutely needs at the time that it’s initialized or loaded:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Detail View Controller import UIKit class RedPillViewController: UIViewController { @IBOutlet weak private var mainLabel: UILabel! // The Implicitly unwrapped optional! var mainText: String! override func viewDidLoad() { super.viewDidLoad() // it's used here! mainLabel.text = mainText } } |
In this case, if mainText is NOT assigned in prepareForSegue, this view controller will immediately crash 💥 This will make it super easy for the developer writing this code to immediately address the problem and assign the dependency:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// In the Parent / More General View Controller override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { switch segueIdentifierForSegue(segue) { case .TheRedPillExperience: let redPillVC = segue.destinationViewController as! RedPillViewController // assigning the implicitly unwrapped optional here! redPillVC.mainText = "😈" case .TheBluePillExperience: let bluePillVC = segue.destinationViewController as! BluePillViewController // assigning the implicitly unwrapped optional here! bluePillVC.mainText = "👼" } } |
The key is to make sure that the implicitly unused optional is assigned or used during the detail view controller’s initialization / view loading cycle! If it’s used later on, the view controller will not crash when the implicitly unwrapped optional is passed in as @justMaku points out:
@NatashaTheRobot I'm talking about situation like this, this will work as long we don't call doFeatureX. pic.twitter.com/RyjsU4quWf
— Michał Kałużny (@justMaku) March 27, 2016
As I mentioned, this is not the perfect solution, just one I’m ok living with compared to alternatives giving the constraints of UIKit.
You can view the full sample project on Github here.