Fixing Storyboard Segues: Only Apple Can Do This
Storyboard segues are broken.
Specifically, the fact that you can’t inject required dependencies into a UIViewController
created and presented as part of a segue is a fundamental problem.
Typical code that does this might look like this example, from That Thing In Swift:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "profile" {
let currentUser = User(name: "Nick", userpicURL: NSURL(string: "https://thatthinginswift.com/profile.png"))
let dest = segue.destinationViewController as! ProfileViewController
dest.profileUser = currentUser
}
}
You could clean that up with some constants representing your segues; maybe you even use a tool like SwiftGen to generate those constants, removing one of the many sharp edges Storyboards provide. But it’s still a stringly-typed API, and you still can’t initialize the view controller with its dependencies.
Recently a new thoughtbot repo called Perform has popped up, promising “Easy dependency injection for storyboard segues.”
With this tool, you can perform a segue and set properties on the new view controller much more cleanly:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: NSIndexPath) {
let task = taskList[indexPath.row]
perform(.showTaskDetails) { taskVC in
taskVC.task = task
}
}
This is an improvement. If you’re using segues in your project (I’m sorry), you should probably use this tool. You might even write a template that lets SwiftGen generate your Segue
extensions for you.
But really, this tool only papers over a small part of the fundamental problem.
In the example code above, how do you know how the taskVC
needs to be configured? What properties do you need to set in order for it to work?
The answer is, of course, you don’t. You need to to look at the TaskDetailsViewController
class, hope it’s documented, or have some a priori knowledge, and then you set some properties and hope it works.
Then, within TaskDetailsViewController
, how do you get to those properties? They were set after the view controller was initialized, so they’re optional. Unwrapping those everywhere is going to be painful. And what do you do if one is missing? If it’s something you really need, you crash at runtime. Maybe you mark these as implicitly unwrapped optionals, which has the same effect, but with less boilerplate. That’s still clearly problematic.
Plus, they’re all declared with var
instead of let
. What happens if one changes partway through the view controller’s lifecycle? Maybe it shouldn’t, but who knows how another programmer might use this class in the future?
To recap, no matter what syntactic sugar we put around them, segues are fundamentally broken. You can’t tell what properties you need to set when you’re configuring a view controller, and in implementing a view controller you’re forced to deal with optionality and mutability — or rather, to work around the ways Swift1 helps programmers deal with optionality and mutability.
This is incredibly poor tooling from Apple. And we can put some bandages on top of it.
But fundamentally, this is something only Apple can fix.
1: This isn’t Swift-specific. These problems exist in Objective-C, too. Sure, you can blissfully ignore nil
properties there — at least until one is missing and your users complain of some difficult-to-diagnose problem on the task details screen.
Maybe in a later post I’ll discuss the problems with making view controllers responsible for configuring other view controllers. That’s not a sustainable pattern in larger apps.