Skip to content
Prepare to Grow

iOS Architecture

Coordinators and Navigation Architecture

Model navigation as feature flow, centralize screen construction, and keep destinations independent from route mechanics.

Advanced6 min read2 guided activities

Before you begin

What you will understand

  • Separate navigation intent from route execution.
  • Design a coordinator with explicit ownership and lifetime.
  • Represent routes for deep linking and restoration.
  • Avoid global routers and coordinator retain cycles.
01

Learning stage

Concept Explanation

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.

  • Navigation intent: semantic request such as openCourse(id:).
  • Coordinator: owns construction and transitions for a flow.
  • Route: value representing a valid destination.
  • Child coordinator: temporary owner of a nested flow.
  • Composition root: creates the root coordinator and long-lived dependencies.
  • Deep link: external input mapped to validated route state.
02

Learning stage

Syntax & Examples

The coordinator owns UIKit mechanics while the screen exposes feature-level events.

CoursesCoordinator.swiftswift
@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)
    }
  }
}
Controllers publish semantic choices; the feature coordinator owns dependency assembly and container transitions.
03

Learning stage

Real-World Usage

Connect the architectural concept to maintainable iOS features and real change scenarios.

  • Build onboarding, authentication, checkout, or editor flows with clear completion.
  • Translate universal links into validated feature routes.
  • Give each tab an independent navigation owner.
  • Swap UIKit pushes for SwiftUI path updates without changing domain decisions.
04

Learning stage

Common Mistakes

Identify abstractions and dependencies that add coupling, ambiguity, or unnecessary ceremony.

  • Creating one global router imported by every feature.
  • Keeping completed child coordinators forever.
  • Capturing coordinators strongly in screen callbacks.
  • Putting business rules inside route switches.
  • Encoding whole mutable models in deep-link and restoration routes.
05

Learning stage

Practice Exercises

Apply the design to a small feature and evaluate it through observable change scenarios.

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.

Activity 2

Map and validate a deep link

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.

06

Learning stage

Knowledge Check

Answer each question before revealing the explanation.

QUESTION 1

What should a feature screen usually communicate to its navigation owner?

  1. A.Semantic intent such as a selected course ID
  2. B.A fully constructed global navigation graph
  3. C.Database credentials
  4. D.A raw window reference
Reveal answer

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 should a parent release a child coordinator?

Reveal answer

When the child flow completes or is cancelled and no longer owns active navigation state.

Retaining completed coordinators causes leaks and stale flow state.

07

Learning stage

Interview Preparation

Explain the decision, dependency direction, trade-offs, and evidence—not only the pattern name.

What problem does the Coordinator pattern solve in iOS?

Short answer: It separates feature navigation and screen construction from individual views or view controllers.

Explore the detailed answer

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

  • • How do coordinators work with SwiftUI?
  • • Who owns child-coordinator lifetime?
08

Learning stage

Key Takeaways

Navigation architecture becomes maintainable when screens express intent, feature owners construct destinations, and routes plus lifetimes remain explicit.

  • Separate navigation intent from execution.
  • Scope coordinators and routes by feature.
  • Construct dependencies at flow boundaries.
  • Validate deep links before mutating navigation.
  • Manage child-coordinator ownership and closure captures explicitly.