Activity 1
Build a feature coordinator
Create a course flow with list, detail, lesson, and editor routes. Let screens report semantic actions through closures or delegates.
Hint: Make construction dependencies visible in the coordinator initializer.
iOS Architecture
Model navigation as feature flow, centralize screen construction, and keep destinations independent from route mechanics.
Before you begin
Learning stage
Navigation has two distinct parts: a screen emits intent, and a flow owner decides the destination and presentation. When a view controller creates every next controller—or a SwiftUI child mutates a global router—feature code becomes coupled to construction, dependencies, container mechanics, and distant routes.
A coordinator owns a flow boundary. It builds screens with their dependencies, responds to semantic events, and updates a UINavigationController, tab selection, sheet, or SwiftUI navigation state. Parent coordinators own child coordinators while their flows are active and release them on completion.
Routes make valid destinations explicit and help translate URLs, notifications, and restoration data into navigation state. Do not centralize every route in one application-wide enum automatically. Prefer feature-scoped routes and coordinators, define how invalid external inputs fall back, and keep navigation mutations on the main actor.
Learning stage
The coordinator owns UIKit mechanics while the screen exposes feature-level events.
@MainActor
final class CoursesCoordinator {
enum Route: Hashable {
case course(Course.ID)
case lesson(Course.ID, Lesson.ID)
}
let navigationController: UINavigationController
private let dependencies: AppDependencies
init(navigationController: UINavigationController,
dependencies: AppDependencies) {
self.navigationController = navigationController
self.dependencies = dependencies
}
func start() {
let list = CourseListViewController(loader: dependencies.courseLoader)
list.onSelect = { [weak self] id in self?.show(.course(id)) }
navigationController.setViewControllers([list], animated: false)
}
func show(_ route: Route) {
switch route {
case let .course(id):
let detail = CourseDetailViewController(courseID: id)
detail.onSelectLesson = { [weak self] lessonID in
self?.show(.lesson(id, lessonID))
}
navigationController.pushViewController(detail, animated: true)
case let .lesson(courseID, lessonID):
let lesson = LessonViewController(courseID: courseID, lessonID: lessonID)
navigationController.pushViewController(lesson, animated: true)
}
}
}Learning stage
Connect the architectural concept to maintainable iOS features and real change scenarios.
Learning stage
Identify abstractions and dependencies that add coupling, ambiguity, or unnecessary ceremony.
Learning stage
Apply the design to a small feature and evaluate it through observable change scenarios.
Activity 1
Create a course flow with list, detail, lesson, and editor routes. Let screens report semantic actions through closures or delegates.
Hint: Make construction dependencies visible in the coordinator initializer.
Activity 2
Translate a course-and-lesson URL into feature routes, handle missing IDs safely, and construct the correct stack.
Hint: External input is untrusted and may not describe a valid route.
Learning stage
Answer each question before revealing the explanation.
QUESTION 1
Semantic intent such as a selected course ID
The coordinator can decide construction and presentation without coupling the screen to container mechanics.
QUESTION 2
When the child flow completes or is cancelled and no longer owns active navigation state.
Retaining completed coordinators causes leaks and stale flow state.
Learning stage
Explain the decision, dependency direction, trade-offs, and evidence—not only the pattern name.
Short answer: It separates feature navigation and screen construction from individual views or view controllers.
Screens emit semantic events while a coordinator constructs destinations, injects dependencies, and updates the relevant navigation container. Feature-scoped routes support deep links and restoration. I make coordinator ownership explicit, remove child flows on completion, and avoid a global router when local navigation is sufficient.
Possible follow-ups
Learning stage
Navigation architecture becomes maintainable when screens express intent, feature owners construct destinations, and routes plus lifetimes remain explicit.