In the previous post, we introduced the Coordinator pattern and showed how to implement a simple home-to-detail navigation flow using UIKit for navigation and SwiftUI for views.
But real-world apps often have multiple user flows, each with its own set of screens.
To make navigation scalable and maintainable, we can use sub-coordinators—a perfect fit for complex navigation requirements. In this step-by-step guide, we’ll build a more advanced example: a multi-flow app with a registration flow and main app flow.
What We’re Building
We’ll implement two separate navigation flows managed by sub-coordinators:
Registration Flow: A sequence of onboarding screens:
Sign Up
Profile Setup
Welcome
Main App Flow: A standard list-to-detail navigation:
Home Screen
Detail Screen
These flows will be managed by a RootCoordinator, which determines where the user starts and switches between flows when needed.
![](https://static.wixstatic.com/media/3b7114_11ebd0fb1b7140c0af72ffd30068fad4~mv2.jpg/v1/fill/w_980,h_745,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/3b7114_11ebd0fb1b7140c0af72ffd30068fad4~mv2.jpg)
Here is a basic demonstration of the entire flow in action:
Why Sub-Coordinators?
When apps grow, navigation logic can quickly get out of hand. Sub-coordinators allow us to:
Isolate the navigation logic for specific user flows (e.g., registration vs main app).
Improve maintainability by enforcing separation of concerns.
Easily reuse and test flows independently.
The Root Coordinator
We will begin at the highest level and dig down as we progress. We will have a RootCoordinator which will be solely responsible for moving users between two flows: from the registration flow to the main app flow.
It will conform to the Coordinator protocol we introduced in the previous post: Intro to the Coordinator pattern with SwiftUI and UIKit. You can find the definition below:
Things to note:
Our RootCoordinator holds references to it's sub-coordinators.
All the coordinators share a reference to the UINavigationController, which they use to manipulate the navigation stack.
When our app starts, it starts the correct user flow based on one condition, whether the user already registered or not.
Since we are performing programmatic navigation using UINavigationController, we have plenty of flexibility in terms of manipulating the navigation stack. Notice that when we start the main app flow, we clear the stack so that the main app flow begins with a clean slate. Otherwise it would be possible for users to “go back” to the registration screens even after they completed that flow, that is not good user experience (UX) and leads to confusion.
The Sub-Coordinators: RegistrationCoordinator and MainAppCoordinator
Our RootCoordinator is composed of two sub-coordinators responsible for their own user flows.