Coordinator Architecture
Why Coordinators?
The Coordinator pattern centralizes navigation logic outside of view controllers and views. It provides a single source of truth for navigation flows, making apps easier to test and maintain.
Coordinator Protocol
Define a base protocol that all coordinators conform to.
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
func start()
}
extension Coordinator {
func addChild(_ coordinator: Coordinator) {
childCoordinators.append(coordinator)
}
func removeChild(_ coordinator: Coordinator) {
childCoordinators = childCoordinators.filter { $0 !== coordinator }
}
func removeAllChildren() {
childCoordinators.removeAll()
}
}
App Coordinator
The root coordinator manages the main navigation and child coordinators.
class AppCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
showLogin()
}
func showLogin() {
let loginCoordinator = LoginCoordinator()
loginCoordinator.delegate = self
addChild(loginCoordinator)
loginCoordinator.start()
}
func showMainApp() {
let mainCoordinator = MainTabCoordinator()
addChild(mainCoordinator)
mainCoordinator.start()
}
}
extension AppCoordinator: LoginCoordinatorDelegate {
func loginDidComplete() {
removeAllChildren()
showMainApp()
}
}
Feature Coordinators
Each feature has its own coordinator managing its navigation flow with delegate callbacks to report completion.
Child Coordinators
Managing Child Coordinators
Child coordinators handle specific navigation flows within a feature. They report completion back to the parent via delegate pattern.
class ProfileCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
weak var delegate: ProfileCoordinatorDelegate?
func start() {
let profileVC = ProfileViewController()
profileVC.onEditProfile = { [weak self] in self?.showEditProfile() }
profileVC.onLogout = { [weak self] in self?.delegate?.profileDidLogout() }
}
func showEditProfile() {
let editCoordinator = EditProfileCoordinator()
editCoordinator.delegate = self
addChild(editCoordinator)
editCoordinator.start()
}
}
extension ProfileCoordinator: EditProfileCoordinatorDelegate {
func editDidComplete() { removeChild(self) }
}
Coordinator as ObservableObject
For SwiftUI, make coordinators ObservableObject to drive view state.
class ShopCoordinator: ObservableObject, Coordinator {
var childCoordinators: [Coordinator] = []
@Published var currentView: ShopView = .list
enum ShopView: Hashable {
case list, cart, checkout
}
func start() { currentView = .list }
func showCart() { currentView = .cart }
func showCheckout() { currentView = .checkout }
}
Using Coordinators in SwiftUI
Inject coordinators into the SwiftUI environment for navigation.
struct ShopView: View {
@StateObject var coordinator = ShopCoordinator()
var body: some View {
NavigationStack {
List {
Button("View Cart") { coordinator.showCart() }
}
.navigationDestination(for: ShopCoordinator.ShopView.self) { view in
switch view {
case .cart: CartView()
case .checkout: CheckoutView()
case .list: EmptyView()
}
}
}
.environmentObject(coordinator)
}
}
Coordinator in SwiftUI
NavigationPath with Coordinators
Combine NavigationPath with coordinators for type-safe navigation.
class NavigationCoordinator: ObservableObject {
var path = NavigationPath()
func showDetail(id: String) {
path.append(id)
}
func showSettings() {
path.append(SettingsRoute())
}
func popToRoot() {
path = NavigationPath()
}
}
struct ContentView: View {
@StateObject var coordinator = NavigationCoordinator()
var body: some View {
NavigationStack(path: $coordinator.path) {
HomeView()
.navigationDestination(for: String.self) { id in
DetailView(id: id)
}
.navigationDestination(for: SettingsRoute.self) { _ in
SettingsView()
}
}
.environmentObject(coordinator)
}
}
Sheet and FullScreenCover Coordination
Handle modal presentations through coordinators.
enum SheetType: Identifiable {
case profile, settings, about
var id: String { String(describing: self) }
}
class SheetCoordinator: ObservableObject {
@Published var activeSheet: SheetType?
@Published var activeFullScreenCover: SheetType?
func showProfile() { activeSheet = .profile }
func showSettings() { activeFullScreenCover = .settings }
func dismiss() { activeSheet = nil; activeFullScreenCover = nil }
}
Coordinator Testing
Coordinators make navigation logic testable by mocking delegate callbacks.
class MockLoginCoordinatorDelegate: LoginCoordinatorDelegate {
var loginCompleted = false
func loginDidComplete() { loginCompleted = true }
}
func testLoginFlow() {
let coordinator = LoginCoordinator()
let delegate = MockLoginCoordinatorDelegate()
coordinator.delegate = delegate
coordinator.start()
coordinator.loginButtonTapped()
assert(delegate.loginCompleted)
}
Quiz
1. What is the main benefit of the Coordinator pattern?
2. How do child coordinators communicate completion to parents?
3. How do you integrate coordinators with SwiftUI NavigationStack?
4. What does the start() method do in a coordinator?
Flashcards
Question
What is the Coordinator pattern?
Click to reveal answer
Answer
A pattern that centralizes navigation logic in dedicated coordinator objects, separating it from views and view controllers.
Question
What is the role of an App Coordinator?
Click to reveal answer
Answer
The root coordinator that manages the main navigation structure and creates child coordinators for features.
Question
How do coordinators report navigation completion?
Click to reveal answer
Answer
Through delegate protocols - child coordinators call delegate methods when their flow completes.
Question
How do coordinators work with SwiftUI?
Click to reveal answer
Answer
Make coordinators ObservableObject and use NavigationPath for type-safe navigation binding.
Revision Notes
Key Takeaways
- 1. Coordinators centralize navigation logic outside views
- 2. Child coordinators report completion via delegates
- 3. ObservableObject makes coordinators work with SwiftUI
- 4. NavigationPath provides type-safe navigation
- 5. Coordinators improve testability of navigation flows
Interview Tips
- • Explain why coordinators are better than navigation in view controllers
- • Describe how to handle complex navigation flows with child coordinators
- • Discuss how coordinators make navigation testable
Cheat Sheet
Coordinator Pattern Quick Reference
- Coordinator protocol - Base protocol with start() and childCoordinators
- AppCoordinator - Root coordinator for main navigation
- Child Coordinators - Feature-specific navigation logic
- Delegate pattern - Communication from child to parent
- NavigationPath - Type-safe SwiftUI navigation
- ObservableObject - SwiftUI integration
- start() - Initialize navigation flow
- removeAllChildren() - Clean up child coordinators