---
title: 'iOS Coordinators: A Storyboard Approach'
teaser: 'Leverage the benefits of Coordinators and Storyboards.

  '
tags: ios,swift,xcode
author: Patrick Montalto
published_on: 2019-05-14
---

There’s been a lot of talk in the iOS development community about the
coordinator pattern ever since [Soroush Khanlou](http://khanlou.com/) gave a
talk at NSSpain 2015 and presented his interpretation - and rightfully so. It
aims to solve common problems plaguing many iOS developers who follow MVC/MVVM
architectures but find that their view controllers are taking on too many
responsibilities, often resulting in large view controllers that are difficult
to debug or comprehend at a glance. Let’s examine the pros and cons of the
coordinator pattern, Xcode storyboards, and how to leverage the benefits of
both!

## Coordinator Pattern

There are numerous benefits to adopting the coordinator pattern. These include:

- Moving view controller creation, configuration, and presentation away from
  view controller level and into coordinator level (limit responsibilities of
  view controllers).
- View controllers no longer have to tell their navigation controller (parent)
  to push or present another VC.
- Enable developers to decouple view controllers so that each one has no idea
  what view controller came before or comes next – or even that a chain of view
  controllers exists. Isolating this logic makes the application easier to
  understand and debug as complexity grows.
- The coordinator is in charge of managing the view controller hierarchy so that
  the view controllers and view models only need to be concerned about their
  particular scenes.
- Separation of responsibilities (coordinators, child coordinators).
- Isolate flows of the app so they may be more reusable, testable, and scalable.

There are some downsides that this pattern can bring, however:

- More code (or at least the same amount as before, but spread among other
  objects and segmented by their corresponding responsibilities!)
- Potentially have to rely on multi-level delegation, which can be cumbersome.

As long as we're aware of these potential downsides and try our best to limit
them, then employing the coordinator pattern can serve as a wise choice.

Most examples of the coordinator pattern are with projects that manage the user
interface in code, with little or no storyboard usage. At first glance, it may
appear storyboards and coordinators don’t seem to play along too nicely -
however, this does not have to be the case. There are a lot of benefits with
using storyboards, but some downsides as well.

## Storyboards

Storyboards in Xcode offer a good deal of benefits, including:

- Visualization - can glance at storyboard and have a general understanding of
  the application and its flows
- Well-supported by Apple
- Robust suite of visual design tools
- Friendly to use for designers who are not comfortable designing in code

However, Storyboards do come with their own set of downsides:

- Merge conflicts can be tricky to resolve
- Higher level of customization still requires underlying code in order to
  design via IB
- Large storyboards can have long parsing processes and slow down development
  time
- Large storyboards with segues can quickly become confusing
- Limited control over navigation / presentation logic, have to rely on wiring
  up segues and using `prepareForSegue`

We'll examine how using the coordinator pattern in tandem with storyboards can
help to alleviate some of the downsides of storyboards while leveraging their
benefits.

# Overview and Setup

Let’s walk through an example application where the user can sign in or sign
out. Depending on the complexity of the application, the “signed-in” portion of
the app, as well as the “signed-out” portion of the app, can be encapsulated by
view controllers or coordinators. For simplicity, we’ll use instances of
`UIViewController` to be the entry point into each flow. These can easily be
changed to `Coordinator` later, which we’ll demonstrate.

We’ll start by creating our two flows in storyboard. We’ll utilize
`Main.storyboard` for the “signed-in” flow, and create a new storyboard file,
`Auth.storyboard` for the “signed-out” flow.

| Main.storyboard | Auth.storyboard |
| --------------- | --------------- |
|![Main View Controller in Interface Builder](https://images.thoughtbot.com/blog-vellum-image-uploads/imo5z7igQCCAjNmHYQQa_MainViewController.png) | ![Auth View Controller in Interface Builder](https://images.thoughtbot.com/blog-vellum-image-uploads/BNLvcxLCRmeEfqSO55BW_AuthViewController.png)|

We’ll position a `UIButton` and a `UILabel` within each view controller and
create a corresponding `.swift` file for each: `MainViewController.swift` and
`AuthViewController.swift` We’ll then finish by wiring up the `@IBAction` for
each button and create a corresponding delegate protocol for each view
controller - allowing them to communicate the user interaction event with
another object.

```swift
// MainViewController.swift
import UIKit

protocol MainViewControllerDelegate: AnyObject {
  func didSignOut()
}

final class MainViewController: UIViewController {
  weak var delegate: MainViewControllerDelegate?

  override func viewDidLoad() {
    super.viewDidLoad()
  }

  @IBAction func signOutPressed(_ sender: Any) {
    delegate?.didSignOut()
  }
}
```

```swift
// AuthViewController.swift
import UIKit

protocol AuthViewControllerDelegate: AnyObject {
  func didSignIn()
}

final class AuthViewController: UIViewController {
  weak var delegate: AuthViewControllerDelegate?

  override func viewDidLoad() {
    super.viewDidLoad()
  }

  @IBAction func signInPressed(_ sender: Any) {
    delegate?.didSignIn()
  }
}
```

# Introducing Coordinators

Currently, neither of the new view controllers we’ve created actually have a
delegate to inform about the sign in and sign out events, which means there’s
presently no way to switch between the Main flow and the Auth flow. This is the
perfect opportunity for us to introduce the topmost `Coordinator`. Let’s begin
with a simple protocol to define the interface of any `Coordinator`.

```swift
protocol Coordinator {
  func start()
}
```

The `start()` function implemented by each type conforming to `Coordinator` will
serve as the entry point for that flow - this means instantiating, configuring,
and presenting the necessary view controllers.

Our first implementation will be an `AppCoordinator` that sits in the window as
a property of `AppDelegate`. This can serve to offload high-level context
switches and serve as the entry point to the application after
`didFinishLaunchingWithOptions` is called.

```swift
// AppCoordinator.swift

import UIKit

final class AppCoordinator: Coordinator {
  // MARK: - Properties
  private let navController: UINavigationController
  private let window: UIWindow

  // MARK: - Initializer
  init(navController: UINavigationController, window: UIWindow) {
    self.navController = navController
    self.window = window
  }

  func start() {
    window.rootViewController = navController
    window.makeKeyAndVisible()
    showMain()
  }

  // MARK: - Navigation
  private func showMain() {
    let mainVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "mainViewController") as! MainViewController
    mainVC.delegate = self
    navController.setViewControllers([mainVC], animated: true)
  }

  private func showAuth() {
    let authVC = UIStoryboard(name: "Auth", bundle: nil)
    .instantiateViewController(withIdentifier: "authViewController") as! AuthViewController
    authVC.delegate = self
    navController.setViewControllers([authVC], animated: true)
  }
}

// MARK: - MainViewControllerDelegate
extension AppCoordinator: MainViewControllerDelegate {
  func didLogOut() {
    User.logout()
    showAuth()
  }
}
```

```swift
// AppDelegate.swift

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  var app: AppCoordinator?

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let window = UIWindow(frame: UIScreen.main.bounds)
    let navController = UINavigationController()
    let appCoordinator = AppCoordinator(navController: navController, window: window)
    app = appCoordinator

    appCoordinator.start()
    return true
  }
}
```

Here, we instantiate a `UIWindow`  and `UINavigationController` in the
`AppDelegate`, injecting it into an instance of `AppCoordinator` that will be a
property of the `AppDelegate`. Finally, we call `start` to begin actually
displaying content.

Within the `AppCoordinator`, we implement the `start` function by assigning the
`window`'s root view controller to our injected `navController`, make `window`
the key window and show it. Finally, within `showMain`, we instantiate the
`MainViewController` and set it as the presented controller of the
`navController`.  Calling `setViewControllers:animated:` on the
`AppCoordinator`’s `navController` will ultimately serve as our way to change
flows in the application, deallocating unused coordinators and their subsequent
view controllers when necessary - such as an application with an onboarding
flow, authentication flow, and a main (authenticated) flow. Each of the
aforementioned flows would have their own set of coordinators and child
coordinators.

We’ll then conform `AppCoordinator` to the two delegate protocols we defined
earlier - `MainViewControllerDelegate` and `AuthViewControllerDelegate`.
Conforming to these delegate protocols now allows the touch events from each
flow to bubble up to the `AppCoordinator`, allowing it to perform the necessary
navigation action in response.

The final step will be disabling the application from launching into the Main
storyboard by leaving “Main Interface” blank.

![Disabling the initial storyboard in
Xcode](https://images.thoughtbot.com/blog-vellum-image-uploads/mzFWtwIeTnWTeyACjO1V_MainInterface.png)

Build and run the app, and we’ll see we’re able to switch between different
flows of the app with ease - all while offloading navigation and view controller
setup to our `Coordinator`!

![Animation showing signing in and signing
out](https://images.thoughtbot.com/blog-vellum-image-uploads/8Bg29WO5Ty6C2OkQSTHA_Coordinate-test.gif)

Now that the `AppCoordinator` is set up, let’s examine one way to use this
pattern with Storyboards to actually construct and design our view controllers
and views.

# Using UIStoryboard extension to init view controllers

The way we are currently instantiating view controllers isn’t optimal. Why?  As
a developer, we’re required to be aware of which view controller resides in
which storyboard. This may be easy right now since the application is very
simple, but that can easily change over time. It’s important to think about
scalability sooner than later. We also have to rely on knowing the string
identifier of the view controller at the call site. This also isn’t ideal.

In this case, one potential solution is to create an extension on `UIStoryboard`
and have it serve as a factory for view controllers. We’ll start by creating
static variables to reference all the storyboards in use in the application.
Then, we’ll expose static factory functions that return the desired subclass of
`UIViewController`.  This results in a method signature that feels very similar
to that which we had previously, but removes the unnecessary responsibility of
the coordinator/view controller having to have as much context in order to
instantiate the desired view controller. Here, we’re using [SOLID design
principles](https://stackify.com/solid-design-principles/), like the single
responsibility principle, and containing responsibilities to those which are
more apt to have context about the particular problem we’re solving.

```swift
// UIStoryboard.swift

import UIKit

extension UIStoryboard {
  // MARK: - Storyboards
  private static var main: UIStoryboard {
    return UIStoryboard(name: "Main", bundle: nil)
  }

  private static var auth: UIStoryboard {
    return UIStoryboard(name: "Auth", bundle: nil)
  }

  // MARK: - View Controllers
  static func instantiateMainViewController(delegate: MainViewControllerDelegate) -> MainViewController {
    let mainVC = main.instantiateViewController(withIdentifier: "mainViewController") as! MainViewController
    mainVC.delegate = delegate
    return mainVC
  }

  static func instantiateAuthViewController(delegate: AuthViewControllerDelegate) -> AuthViewController {
    let authVC = auth.instantiateViewController(withIdentifier: "authViewController") as! AuthViewController
    authVC.delegate = delegate
    return authVC
  }
}
```

We can then refactor `showMain()` and `showAuth()` in our `AppCoordinator` to
use this extension.

```swift
// MARK: - Navigation
private func showMain() {
  let mainVC = UIStoryboard.instantiateMainViewController(delegate: self)
  navController.setViewControllers([mainVC], animated: true)
}

private func showAuth() {
  let authVC = UIStoryboard.instantiateAuthViewController(delegate: self)
  navController.setViewControllers([authVC], animated: true)
}
```

# Child Coordinators

We’re currently using `UIViewControllers` as the entry point to each flow. As
applications grow in complexity, these view controllers are bound to have their
own navigation component. We can refactor `showMain` and `showAuth` in our
`AppCoordinator` to instead call `start` on two new child coordinators:
`AuthCoordinator` and `MainCoordinator`. These coordinators can then be
responsible for handling the navigation within each subsequent flow. For
brevity, we’ll just focus on implementing the `AuthCoordinator` in this post.

```swift
// AuthCoordinator.swift

import UIKit

protocol AuthCoordinatorDelegate: AnyObject {
  func didAuthenticate()
}

final class AuthCoordinator: Coordinator {
  private let navController: UINavigationController
  weak var delegate: AuthCoordinatorDelegate?

  init(navController: UINavigationController, delegate: AuthCoordinatorDelegate) {
    self.navController = navController
    self.delegate = delegate
  }

  func start() {
    let authVC = UIStoryboard.instantiateAuthViewController(delegate: self)
    navController.setViewControllers([authVC], animated: true)
  }
}

extension AuthCoordinator: AuthViewControllerDelegate {
  func didSignIn() {
    // Authenticate via API, etc...
    delegate?.didAuthenticate()
  }
}
```

We’ll create the `AuthCoordinator` by conforming to the `Coordinator` protocol
we defined earlier. We’ll create an initializer that accepts a
`UINavigationController` along with an object that conforms to a newly-defined
protocol,  `AuthCoordinatorDelegate`. The `navController` injected into
`AuthCoordinator` will be used to mutate the navigation stack when this
coordinator wants to `start`.

Instead of `AppCoordinator` being the delegate for the `AuthViewController`, the
`AuthCoordinator` will. Conforming to `AuthViewControllerDelegate` lets us
implement `didSignIn`, which is where we could perform a network request to
authenticate the user - offloading this responsibility to initiate this request
from the `AuthViewController` itself. Depending on the outcome of the
authentication request, we could then inform the delegate that authentication
was successful via `didAuthenticate`.

We will then make the necessary changes in `AppCoordinator`. We start by adding
a private variable to retain `AppCoordinator`'s child coordinators, which for
now will be `AuthCoordinator`. We need to hold a reference to these child
coordinators as we do not want them to be deallocated too soon.

```swift
private var childCoordinators: [Coordinator] = []
```

We then modify `showAuth` to make use of the `AuthCoordinator`, instead of
instantiating the `AuthViewController` directly.

```swift
private func showAuth() {
  let authCoordinator = AuthCoordinator(navController: navController, delegate: self)
  childCoordinators.append(authCoordinator)
  authCoordinator.start()
}
```

Updating `showMain` will also be necessary, as we want to release the
`AuthCoordinator` and its children when we switch to the main, signed-in flow of
the application. In order to achieve this, we’ll remove all instances of
`AuthCoordinator` from `childCoordinators`.

```swift
private func showMain() {
  let mainVC = UIStoryboard.instantiateMainViewController(delegate: self)
  navController.setViewControllers([mainVC], animated: true)
  childCoordinators.removeAll { $0 is AuthCoordinator }
}
```

Finally, we’ll no longer conform to `AuthViewControllerDelegate`, but instead,
`AuthCoordinatorDelegate`. Conforming to this protocol allows the
`AppCoordinator` to change flows of the app depending on the authentication
request result within `AuthCoordinator`.

```swift
extension AppCoordinator: AuthCoordinatorDelegate {
  func didAuthenticate() {
    showMain()
  }
}
```

## Wrap Up

We now have an excellent starting point to further develop these flows.
Responsibilities of each `Coordinator` are clearly defined and we’ve prevented
navigation and view controller configuration/instantiation logic from entering
the view controllers themselves.

The code for this project can be found
[here](https://github.com/thoughtbot/coordinate).
