Mastering Navigation With Coordinators

Storyboards, XIBs, and Code

The Photos project exclusively uses storyboards to design and create view controllers. In this episode, I show you that the coordinator pattern isn't limited to storyboards. You can also use the coordinator pattern if you prefer XIB files over storyboards. If you prefer to use neither, then that's possible too. It doesn't matter how you design and create the view controllers of your project.

Starter Project

The starter project of this episode is functionally identical to the finished project of the previous episode. There are a few differences I want to point out, though. The TermsViewController class no longer conforms to the Storyboardable protocol and it now has three subclasses, TermsViewControllerStoryboard, TermsViewControllerXIB, and TermsViewControllerCode.

The TermsViewControllerStoryboard class is identical to the TermsViewController class of the previous episode. The user interface of the TermsViewControllerStoryboard class is defined in Main.storyboard. We can instantiate an instance of the class by invoking the instantiate() method of the Storyboardable protocol.

The user interface of the TermsViewControllerXIB class is defined in TermsViewControllerXIB.xib. The TermsViewControllerXIB class defines a static, variable property, nibName, to avoid the use of string literals. This is similar to the storyboardIdentifier property defined in Storyboardable.swift.

As the name implies, the TermsViewControllerCode class doesn't have a link to a storyboard or a XIB file. The user interface is created in code. This obviously results in quite a bit more code. Let's take a look at each of these view controllers and explore how they fit into the coordinator pattern.

Storyboards

Storyboards have been around for several years and Apple encourages developers to use them. I use storyboards in most projects I work on. Xcode automatically creates a storyboard when you create a brand new project, but I strongly recommend to use several smaller storyboards instead of one large storyboard. By using several smaller storyboards, you can avoid merge conflicts more easily and working with smaller storyboards is faster and less complex.

Segues are a key feature of storyboards, but I never use them in combination with the coordinator pattern. While it is possible to use segues in combination with the coordinator pattern, a segue involves the view controller in the application flow and that is something we want to avoid. Remember that the coordinators of the project handle the application flow. A view controller doesn't need to know anything about the application flow and it doesn't need to know about other view controllers.

Even without segues, storyboards have their value. I always split medium to large projects up into modules. Each module has a storyboard, which contains the view controllers of the module.

Open TermsCoordinator.swift and navigate to the showTerms() method. The implementation is identical to that of the finished project of the previous episode. The only difference is the name of the view controller class. The terms coordinator instantiates the TermsViewControllerStoryboard class instead of the TermsViewController class.

// MARK: - Helper Methods

private func showTerms() {
    // Initialize Terms View Controller
    let termsViewController = TermsViewControllerStoryboard.instantiate()

    // Install Handlers
    termsViewController.didCancel = { [weak self] in
        self?.finish()
    }

    // Present Terms View Controller
    presentingViewController.present(termsViewController, animated: true)
}

XIBs

XIB files have been around since the early days of Cocoa development. They lack a few features compared to storyboards, but they still have their value. I primarily use XIBs for reusable components, such as table view cells and collection view cells.

Open TermsViewControllerXIB.xib. The user interface of the TermsViewControllerXIB class is identical to that of the TermsViewControllerStoryboard class in Main.storyboard. Open TermsCoordinator.swift and revisit the showTerms() method. To create an instance of the TermsViewControllerXIB class, we invoke the init(nibName:bundle:) initializer, passing in the name of the XIB file and a reference to the bundle containing the XIB file.

// MARK: - Helper Methods

private func showTerms() {
    // Initialize Terms View Controller
    let termsViewController = TermsViewControllerXIB(nibName: TermsViewControllerXIB.nibName, bundle: .main)

    // Install Handlers
    termsViewController.didCancel = { [weak self] in
        self?.finish()
    }

    // Present Terms View Controller
    presentingViewController.present(termsViewController, animated: true)
}

Code

It's also possible to create the user interface of the view controllers of the project in code. If you don't like storyboards or XIB files, then this is your only option. I haven't been using this option very much since the introduction of storyboards, but it can be useful from time to time.

Creating and initializing a view controller in code is useful if that view controller has a complex or dynamic user interface. Creating and initializing view controllers in code has another compelling benefit. It allows for initializer injection, a form of dependency injection. I discuss dependency injection in the next episode.

To create an instance of the TermsViewControllerCode class, we invoke the init() initializer. This is equivalent to invoking the init(nibName:bundle:) initializer and passing in nil for both arguments.

// MARK: - Helper Methods

private func showTerms() {
    // Initialize Terms View Controller
    let termsViewController = TermsViewControllerCode()

    // Install Handlers
    termsViewController.didCancel = { [weak self] in
        self?.finish()
    }

    // Present Terms View Controller
    presentingViewController.present(termsViewController, animated: true)
}

What's Next?

The coordinator pattern is a flexible pattern. It doesn't matter if you use storyboards, XIB files, or neither. You can use segues, but remember that you're violating a fundamental principle of the coordinator pattern if you do. A view controller doesn't need to know about the application flow and it shouldn't know about other view controllers.

Next Episode "Dependency Injection and the Coordinator Pattern"