# Supercharge Your Coordinators for Maximum Efficiency

[Gleb Arkhipov](https://www.strv.com/blog/authors/gleb) iOS Engineering Manager

---

If you haven't heard about the coordinator pattern in iOS, definitely check it out. This architectural solution was designed to battle massive view controllers and was invented by Soroush Khanlou, who thoroughly describes the reasoning behind it [here](https://khanlou.com/2015/01/the-coordinator/?ref=strv.ghost.io).

Coordinators provide a smart Swifty way to extract all the logic related to view controller creation and their navigation to a separate layer. It helps to maintain thin view controllers, taking away one of their usual responsibilities since they can grow in size very easily and become very complicated to wrap your head around. There are several popular implementations, but we will stick with this one.

```swift
protocol Coordinator: AnyObject {
    var childCoordinators: [Coordinator] { get set }
    func start()
}
```

`var childCoordinators` will contain child coordinators, and we will take a look at them later. `func start()`, you guessed it right, will start the flow and do all the needed setup. Usually the protocol will also be extended with:

```swift
protocol NavigationControllerCoordinator: Coordinator {
    var navigationController: UINavigationController { get }
}
```

...since a coordinator is associated with a navigation component. A typical coordinator can be implemented as:

```swift
class LoginCoordinator: NavigationControllerCoordinator {
    func showMainDashboard() { ... }
    func cancel() { ... }

    var navigationController: UINavigationController = UINavigationController()
    var childCoordinators: [Coordinator] = []

    func start() {
        let signInViewController = makeSignInViewController()
        signInViewController.coordinator = self
        navigationController.setViewControllers(
            [signInViewController],
            animated: false
        )
    }

    func makeSignInViewController() -> SignInViewController { ... }
}
```

How will it look like inside our view controllers?

```swift
class SignInViewController: UIViewController {
    weak var coordinator: LoginCoordinator?
}
```

Although this works okay, it creates a strong bond between `LoginCoordinator` and `SignInViewController`. Imagine a situation where `SignInViewController` is shared with another coordinator in a different flow. It turns out incompatible because the coordinator type is no longer `LoginCoordinator`. Meaning, we can do better — by defining `LoginCoordinating` and changing `weak var coordinator: LoginCoordinating?`.

```swift
protocol LoginCoordinating: AnyObject {
    func showMainDashboard()
    func cancel()
}

class SignInViewController: UIViewController {
    weak var coordinator: LoginCoordinating?
}
```

However, I still have a feeling that `SignInViewController` sort of knows what is coming next in the login flow. What if we add a little bit of abstraction, reversing the idea of this protocol so that it accepts events generated by `SignInViewController` rather than exposing the `LoginCoordinator` functionality:

```swift
protocol SignInViewControllerEventHandling: AnyObject {
    func handle(event: SignInViewController.Event)
}

enum SignInViewController {
    enum Event {
        case login
        case dismiss
    }
}

weak var coordinator: SignInViewControllerEventHandling?
```

In this case, `LoginCoordinator` conforms to `SignInViewControllerEventHandling` and handles all the events coming from the *view controller*, whereas `SignInViewController` just calls its coordinator using `coordinator?.handle(event: .login)` and has no information about hierarchy changes. The view controller can even pass data using enum's associated type. The best part is that every coordinator conforming to `SignInViewControllerEventHandling` can now manage `SignInViewController` by pushing or presenting new screens on top of it.

Some people will argue that this approach creates huge switches inside coordinators managing multiple screens, and this is partially true. Technically speaking, creating a separate method for each event inside the protocol is faster than having a single `handle(event:)`, but sometimes a coordinator manages only a subset of events that are produced by a view controller, which can be elegantly handled in a switch. Having many unused methods requires empty implementations in order to conform to the protocol, which is a lot of useless code. In the end, I’d say it’s a matter of personal preference.

It is also possible to define a helper protocol that all *view controllers* conform to; however, it only enforces creating a `coordinator` property of any type:

```swift
public protocol Coordinated: AnyObject {
    associatedtype Coordinator
    var coordinator: Coordinator? { get set }
}
```

Let's rewind back to child coordinators and `var childCoordinators` in the `Coordinator` protocol. Application hierarchy consists of many screens and flows; hence there are multiple coordinators. How can we take advantage of this and express flow dependencies using child coordinators? Well, as soon as the new flow starts, we’ll be adding the new coordinator to `childCoordinators`.

```swift
class MainCoordinator: TabBarControllerCoordinator {
    func showSettings() {
        let settingsCoordinator = SettingsCoordinator()
        settingsCoordinator.start()
        childCoordinators.append(settingsCoordinator)
        tabBarController.present(
            settingsCoordinator.navigationController,
            animated: true
        )
    }
}
```

This creates a tree-like structure with `AppCoordinator` at the root, which becomes a map of the app at any given time. While adding child coordinators seems like a simple task, removing them is a bit tricky. In a nutshell, there has to be a way for each coordinator to delete itself from the parent's `childCoordinators`. We can utilize the same idea used for view controllers by defining a new protocol called `ParentCoordinated`.

```swift
protocol ParentCoordinated: AnyObject {
    associatedtype Parent
    var parent: Parent? { get set }
}
```

Then our coordinator can send special events to its parent.

```swift
protocol LoginCoordinatorEventHandling: AnyObject {
    func handle(event: LoginCoordinator.Event)
}

class LoginCoordinator: NavigationControllerCoordinator, ParentCoordinated {
    case dismiss(coordinator: Coordinator)

    weak var parent: LoginCoordinatorEventHandling?

    func dismiss() {
        parent?.handle(event: .dismiss(coordinator: self))
    }
}

class AppCoordinator: LoginCoordinatorEventHandling {
    func handle(event: LoginCoordinator.Event) {
        switch event {
        case .dismiss(let coordinator):
            childCoordinators.removeAll { $0 === coordinator }
            navigationController.dismiss(animated: true)
        }
    }
}
```

Although we have almost saved ourselves from memory leaks, there should be a place *inside view controllers* where we send a dismiss event once a hierarchy needs to change. I can see three common scenarios:

- View controller was presented modally
- View controller was pushed with a different coordinator on the same navigation stack
- View controller is shown via a custom transition

In case of modals, the best place to put `coordinator?.handle(event: .dismiss)` will be right before the view controller should be dismissed, like in `IBAction` or a button's target.

What about iOS 13 modals that can be dismissed by a pull? You might ask. There is no silver bullet, but you can implement `func presentationControllerDidDismiss(_ presentationController: UIPresentationController)` from `UIAdaptivePresentationControllerDelegate` and then assign `navigationController.presentationController?.delegate` to handle it. A custom navigation controller or a coordinator are good candidates for the job.

The second case requires overriding the back button or making use of `isMovingFromParent` in `viewDidDisappear`, where the event is passed to the coordinator and then forwarded to the parent for coordinator deletion:

```swift
override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    if isMovingFromParent {
        coordinator?.handle(event: .dismiss)
    }
}
```

You can also take a look at `func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)` from `UINavigationControllerDelegate` and `navigationController.transitionCoordinator?.viewController(forKey: .from)` to determine whether the view controller is still present in the hierarchy and delete its coordinator. This approach will handle everything in one place without adding any code to the view controllers, but it will be triggered for every push/pop, making it somewhat less performant.

The last case should be handled once a dismiss transition is over, so it will heavily depend on the implementation.

## BONUS: PRACTICAL ADDITIONS

Once everything is in place, coordinators become routes to every part of your app, making them very convenient for deep linking. You can extend the **`Coordinator` protocol** with a method handling a deep link which allows to pass it from **`AppDelegate/SceneDelegate`** to a final screen via **`AppCoordinator`** and its subsequent children:

```swift
protocol Coordinator: AnyObject {
    func start()
    func handle(deeplink: Deeplink)
}
```

Coordinators are also a great starting point for **A/B testing** whenever a new flow or a screen should be shown based on a particular condition. You can even have a distinct coordinator to abstract the differences of the new flow:

```swift
class LoginCoordinator: NavigationControllerCoordinator {
    func showOnboarding() {
        guard CapabilityService.isEligibleForNewOnboarding else {
            navigationController.present(OldOnboardingViewController(), animated: true)
            return
        }
        navigationController.present(NewOnboardingViewController(), animated: true)
    }
}
```

Since one of the coordinator tasks is view controller creation, it can provide shelter for your **dependency injection container** or maybe a factory instance, if you are using one. Last but not least, we can count the number of apps you have seen that used the `NSNotificationCenter` API to send notifications to remote parts of the app in order to increment some badges or propagate logout events. I’d bet it’s been a few.

Now you can have a typesafe technique that will distribute a payload to the farthest island screen in your app.

Thank you for sticking with me until the end. I hope you found something inspiring in all the information!

P.S. Check out a sample app showcasing this pattern at [https://github.com/arkhigleb/coordinators](https://github.com/arkhigleb/coordinators).