In the previous episodes, we added support for a vertical purchase flow. Most applications are a puzzle of horizontal and vertical flows. Combining horizontal and vertical flows allows for flexible and dynamic application flows.
The current implementation of the coordinator pattern has a limitation I would like to remove in this episode. The application coordinator is the only coordinator that can manage child coordinators. That ability shouldn't be restricted to the application coordinator.
The plan of action is simple. By moving the ability to manage child coordinators to the Coordinator class, any coordinator inheriting form the Coordinator class automatically has the ability to push and pop child coordinators.
To test the changes we make in this episode, we add a new subflow to the application. The goal is to initiate a vertical flow from the buy view controller. Remember that the buy view controller is managed by the buy coordinator. Let me show you what we start with.
Exploring the Terms View Controller
The only difference with the finished project of the previous episode is the addition of the TermsViewController class. Open TermsViewController.swift in the Terms View Controller group, a subgroup of the View Controllers group. The TermsViewController class conforms to the Storyboardable protocol and its implementation is basic. It defines a handler with name didCancel and a cancel(_:) method. The didCancel property is of an optional type, a closure that accepts no arguments. The didCancel handler is invoked in the cancel(_:) method. That's it.
import UIKit
class TermsViewController: UIViewController, Storyboardable {
// MARK: - Properties
var didCancel: (() -> Void)?
// MARK: - Actions
@IBAction func cancel(_ sender: Any) {
didCancel?()
}
}
To understand the purpose of the TermsViewController class, we need to open Main.storyboard. Navigate to the Terms View Controller scene. The terms view controller presents the terms of service to the user. The Cancel button in the top right invokes the cancel(_:) method of the TermsViewController class. That in turn invokes the didCancel handler of the terms view controller.

Creating a Plan of Action
The goal of this episode is simple. We add a button with title Terms of Service below the Buy button of the buy view controller. When the user taps the Terms of Service button, the terms view controller is presented modally. To make that possible, we need to take a few simple steps.
First, we add a button to the buy view controller and define a handler to notify the buy coordinator when the user taps the button. Second, we create and implement a Coordinator subclass that manages the vertical flow, that is, presenting the terms view controller. Third, the BuyCoordinator class should be capable of initiating the coordinator that manages the terms view controller. In other words, the BuyCoordinator class should have the ability to manage child coordinators. Let's start with the first step.
Defining a Handler
Open BuyViewController.swift and define a handler with name didShowTerms. The didShowTerms property is of an optional type, a closure that accepts no arguments.
var didShowTerms: (() -> Void)?
We also need to create an action that invokes the didShowTerms handler. Create an action with name showTerms(_:). In the body of the showTerms(_:) action, we invoke the didShowTerms handler.
@IBAction func showTerms(_ sender: Any) {
didShowTerms?()
}
Open Main.storyboard and navigate to the Buy View Controller scene. Add a button to the vertical stack view, below the Buy button. Set the title of the button to Terms of Service. Select the buy view controller and open the Connections Inspector on the right. Connect the showTerms(_:) action to the Touch Up Inside event of the Terms of Service button.

Implementing the Terms Coordinator
The next step is creating and implementing a coordinator that manages the terms view controller. This should be straightforward by now. Add a new Swift file to the Child Coordinators group and name it TermsCoordinator.swift. Add an import statement for the UIKit framework and define a class with name TermsCoordinator. The TermsCoordinator class subclasses the Coordinator class.
import UIKit
class TermsCoordinator: Coordinator {
}
We want to present the terms view controller modally, which means the terms coordinator manages a vertical flow. For that to work, the terms coordinator needs a reference to a view controller that can present the terms view controller. This is similar to how we implemented the vertical purchase flow in the previous episodes. Define a private, constant property, presentingViewController, of type UIViewController.
import UIKit
class TermsCoordinator: Coordinator {
// MARK: - Properties
private let presentingViewController: UIViewController
}
The reference to the presenting view controller is passed to the terms coordinator during initialization. We create an initializer that accepts a UIViewController instance as its only argument. In the initializer, we store a reference to the UIViewController instance in the presentingViewController property.
import UIKit
class TermsCoordinator: Coordinator {
// MARK: - Properties
private let presentingViewController: UIViewController
// MARK: - Initialization
init(presentingViewController: UIViewController) {
// Set Presenting View Controller
self.presentingViewController = presentingViewController
}
}
The next step is overriding the start() method. In the start() method, we invoke a helper method, showTerms().
// MARK: - Overrides
override func start() {
// Show Terms
showTerms()
}
The implementation of the showTerms() method should look familiar by now. We instantiate an instance of the TermsViewController class by invoking the instantiate() class method. We install the didCancel handler of the terms view controller. In the closure we assign to the didCancel handler, we invoke another helper method, finish(). We implement the finish() method in a moment. To present the terms view controller, the terms coordinator invokes the present(_:animated:completion:) method on the presenting view controller, passing in a reference to the TermsViewController instance.
// MARK: - Helper Methods
private func showTerms() {
// Initialize Terms View Controller
let termsViewController = TermsViewController.instantiate()
// Install Handlers
termsViewController.didCancel = { [weak self] in
self?.finish()
}
// Present Terms View Controller
presentingViewController.present(termsViewController, animated: true)
}
The finish() method is declared privately. Its implementation should also look familiar. In the body of the finish() method, the terms coordinator invokes the dismiss(animated:completion:) method on the presenting view controller and it invokes the didFinish handler to notify the parent coordinator of the terms coordinator.
// MARK: - Private API
private func finish() {
// Dismiss Terms View Controller
presentingViewController.dismiss(animated: true)
// Invoke Handler
didFinish?(self)
}
That's it. I hope you agree that the implementation of the TermsCoordinator class isn't complex. Notice that the terms coordinator doesn't manage a navigation controller. Because it presents a single view controller, there's no need for a navigation controller. If the TermsCoordinator class were to manage a vertical flow with multiple view controllers, then we would need a navigation controller.
Updating the Coordinator Class
Only the AppCoordinator class is currently capable of managing child coordinators. This is easy to change, though. Open Coordinator.swift on the left and AppCoordinator.swift in the assistant editor on the right. We need to make two changes. First, move the childCoordinators property from the AppCoordinator class to the Coordinator class and remove the private keyword.
import UIKit
class Coordinator: NSObject, UINavigationControllerDelegate {
// MARK: - Properties
var didFinish: ((Coordinator) -> Void)?
// MARK: -
var childCoordinators: [Coordinator] = []
// MARK: - Methods
func start() {}
// MARK: -
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {}
}
Second, move pushCoordinator(_:) and popCoordinator(_:) from the AppCoordinator class to the Coordinator class and remove the private keywords.
import UIKit
class Coordinator: NSObject, UINavigationControllerDelegate {
// MARK: - Properties
var didFinish: ((Coordinator) -> Void)?
// MARK: -
var childCoordinators: [Coordinator] = []
// MARK: - Methods
func start() {}
// MARK: -
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {}
// MARK: -
func pushCoordinator(_ coordinator: Coordinator) {
// Install Handler
coordinator.didFinish = { [weak self] (coordinator) in
self?.popCoordinator(coordinator)
}
// Start Coordinator
coordinator.start()
// Append to Child Coordinators
childCoordinators.append(coordinator)
}
func popCoordinator(_ coordinator: Coordinator) {
// Remove Coordinator From Child Coordinators
if let index = childCoordinators.firstIndex(where: { $0 === coordinator }) {
childCoordinators.remove(at: index)
}
}
}
These are the only changes we need to make. Any Coordinator subclass is now capable of managing one or more child coordinators. Build the project to make sure we didn't break anything. The compiler doesn't throw any errors, which is a good sign.
Presenting the Terms View Controller
The last step is presenting the terms view controller when the user taps the Terms of Service button of the buy view controller. Open BuyCoordinator.swift and navigate to the buyPhoto(_:) method. We need to install the didShowTerms handler of the buy view controller. In the closure we assign to the didShowTerms handler, we invoke a helper method, showTerms().
private func buyPhoto(_ photo: Photo) {
// Initialize Buy View Controller
let buyViewController = BuyViewController.instantiate()
// Configure Buy View Controller
buyViewController.photo = photo
// Install Handlers
buyViewController.didBuyPhoto = { [weak self] _ in
// Update User Defaults
UserDefaults.buy(photo: photo)
// Finish
self?.finish()
}
buyViewController.didCancel = { [weak self] in
self?.finish()
}
buyViewController.didShowTerms = { [weak self] in
self?.showTerms()
}
// Push Buy View Controller Onto Navigation Stack
navigationController.pushViewController(buyViewController, animated: true)
}
The implementation of the showTerms() method is straightforward. We initialize an instance of the TermsCoordinator class, passing the navigation controller of the buy coordinator to the initializer. The navigation controller is the presenting view controller of the terms coordinator. Because the TermsCoordinator class is a Coordinator subclass, we can invoke the pushCoordinator(_:) method, passing in the TermsCoordinator instance. That's it. The details are handled by the Coordinator class.
private func showTerms() {
// Initialize Terms Coordinator
let termsCoordinator = TermsCoordinator(presentingViewController: navigationController)
// Push Terms Coordinator
pushCoordinator(termsCoordinator)
}
Build and run the application to see the result. Make sure the user is signed out. Select a photo in the table view of the photos view controller and tap the Buy button in the top right to initiate the purchase flow. Sign in and tap the Terms of Service button of the buy view controller to present the terms view controller. The purchase flow is presented as a horizontal flow. The buy coordinator initiates the terms flow as a vertical flow. This shows that the solution we implemented works as expected.
Let's test the vertical purchase flow. Tap the Cancel button of the terms view controller to return to the buy view controller. Tap the Cancel button of the buy view controller to cancel the purchase flow. Return to the photos view controller and tap the Sign Out button in the top right to sign out. Tap the Buy button of a table view cell in the table view to initiate the vertical purchase flow. Sign in and tap the Terms of Service button of the buy view controller to present the terms view controller. Presenting the terms view controller from the vertical purchase flow also works as expected.
What's Next?
We successfully added the ability to the Coordinator class to push and pop child coordinators. Every Coordinator subclass is now capable of initiating subflows.