Don’t make a new view controller by mistake, part 2

As I said in my previous post, there’s a common mistake where a programmer creates a new view controller instance when that isn’t what’s really intended.

The typical code looks like this:

let vc = MyViewController()

Often, that’s not the right thing to say. As I said in my previous post, if there is a MyViewController in the view controller hierarchy already and you want to talk to it, saying MyViewController() is not how to do that. Another way to make this mistake is when you want the storyboard to make an instance for you.

Here’s an actual example. This programmer is trying to perform a segue when the user taps on a table view cell:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    self.performSegue(withIdentifier: "pizzaSegue", sender: self.drinks[indexPath.row] as Drink)

But the app is crashing instead, because there is no such segue. But why not? asks our programmer. I’m looking right at the storyboard and I can see that this view controller does have this segue with this identifier coming from it.

No. That’s the problem. self (the view controller that is supposed to perform this segue) is not the same as the view controller we can see in the storyboard. Why not? Because earlier, the programmer said this:

let drinkController = DrinkListTableViewController() 
let drinkNavigationController = UINavigationController(rootViewController: drinkController) 
self.present(drinkNavigationController, animated: true, completion: nil)

There it is, a view controller creation:

let drinkController = DrinkListTableViewController() 

What does that do? It creates a brand new "blank" view controller, with no segues and no outlets – none of the stuff that was configured in the storyboard. The storyboard view controller is a different view controller! But it is the one we want.

How do you get the storyboard view controller? You have to talk to the storyboard and ask it for the desired view controller, by identifier (you might have to give it an identifier in the storyboard in order to do that). For example:

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let drinkController = storyboard.instantiateViewController(withIdentifier: "drinkController") as! DrinkListTableViewController

That is the way to get the view controller that is already configured in the storyboard.

A typical sign that you are making this mistake could be that you crash, as this programmer did. You might crash because you refer to a segue that isn’t there, or to an outlet that is nil — because those things are configured in the storyboard, so you need to storyboard’s view controller instance in order to get them.