I Am Simme

Random musings by a programmer and film photography geek.

The Coordinator Pattern

Posted in iOS.

Soroush Khanlou had a very interesting blog post a while back. In it he elaborates on his "coordinator pattern". Its a very neat pattern in which you make all of your view controllers "flow agnostic". Ie. they know nothing about the view controller hierarchy, when to push another view controller on the navigation stack, when to present a modal etc. All of that responsibility is delegated to a coordinator object. Each view controller becomes completely isolated. If you haven't read his post you should go ahead and do that before continuing here. It's a great read!

I'm currently working on a new app for Filibaba. When the various flows of the app started coming together I quickly realized that many of the view controllers were basically the same, but with small variations in behavior. I had started building the app using storyboards and segues and that was quickly becoming a mess.

Isolation

I took a week to pause feature development and rewrite each view controller. Instead of having direct access to the model layer each view controller now has properties and a delegate that informs the behavior. Instead of pushing a detail view controller on the stack a table view instead notifies its delegate (most likely a coordinator) that a cell was selected. The delegate then creates the detail view controller and pushes it on the stack.

This means that no view controller relies on global state or is in any other way tied to the rest of the environment. So reusing them becomes incredibly easy.

Enter Coordinator

After doing this I was left with very pretty view controllers, but a very broken app. So I took to pen and paper to sketch out each of the flows in the app. Basically a flow chart of the entire app. Doing this I could identify areas where the same type of flow occurred. Each of these areas became their own coordinator.

For example, I had a onboarding flow that was very similar to the edit flow. These became the one and same coordinator.

After having each of the flows down I started thinking about what a coordinator needed to be able to do:

I came up with this protocol (gist, might not show up in RSS readers):

(To simplify things pertaining to storing of child coordinators etc I made the coordinators NSObjects. Generic self constraints and what not. Would be great to get around this somehow.)

The default implementation extension provides convenience methods for starting and stopping a coordinator.

Usage

So imagine you have a view controller showing a contact. The view is displaying an edit button. The view controller is managed by a ContactsBrowsingCoordinator. The user taps that edit button which triggers a delegate call: delegate?.contactDetailViewController(contactDetailViewController: ContactDetailViewController, wantsToEditContact contact: Contact)

The delegate of the view controller is the ContactsBrowsingCoordinator. When the wantsToEdit method is called it spins off a EditContactCoordinator doing something like this:

func contactDetailViewController(contactDetailViewController: ContactDetailViewController, wantsToEditContact contact: Contact) {  
  // The type of coordinator to start is inferred by the type declaration in the block.
  startChildWith(rootViewController, callback: nil) { (coordinator: EditContactCoordinator) in
        // Your chance to set behavioral properties on the `EditContactCoordinator`, like the contact being edited.
        // This block is called _before_ the start method of the coordinator.
        coordinator.contactToEdit = contact

        // This coordinator can be a delegate of the new coordinator to get notified of events. Like when the user is done. This is when this coordinator would call `stop` on the edit coordinator which would then rewind the navigation stack and return to where it kicked off.
        coordinator.delegate = self
  }
}

In the start method of the EditContactCoordinator we create the edit view controller and present it:

func start(withCallback: CoordinatorCallback) {  
  // See: https://medium.com/swift-programming/uistoryboard-safer-with-enums-protocol-extensions-and-generics-7aad3883b44d
  let viewController: EditContactViewController = storyboard.instantiateViewController()

  // Pass on the property we set before
  viewController.contact = contact
  viewController.delegate = self

  rootViewController.presentViewController(viewController, animated: true)
}

Recap

This particular design has made it super simple for me to modify the various flows when we've made changes to ordering. It makes each view controller very simple and easy to read and reason about.

All of the flows are clearly articulated in each coordinator and following along is straight forward. I don't think I'll ever write an app any other way.

Sure, it's a bit of boilerplate. For smaller apps you will end up with more code just to connect two view controllers. But I still think it's worth it in the long run. It also helps you think about what each view controller has to be able to do when you define its delegate protocol. It is easy to see when things start to get out of hand and you might have to split things up further.

I implement each view controller delegate as an extension to my coordinators. This way it is easy to split it up into multiple files and find the bit of code you're looking for.

Many, not all, view controllers are still defined and laid out in a storyboard. I'm just not using segues. Instead I'm using a storyboard extension to make view controller instantiation super simple.

I'd be happy to answer any questions on Twitter: @simmelj. Or in the comments of the gist. If you have any other feedback on how one might improve the Coordinator protocol I'd be super happy, still very new to generics.