0
0
iOS Swiftmobile~15 mins

Coordinator pattern in iOS Swift - Deep Dive

Choose your learning style9 modes available
Overview - Coordinator pattern
What is it?
The Coordinator pattern is a way to organize navigation and flow in an iOS app. It moves the responsibility of moving between screens out of view controllers and into separate coordinator objects. This helps keep the app's structure clean and easier to manage, especially as it grows.
Why it matters
Without the Coordinator pattern, view controllers get overloaded with navigation code, making them hard to read and maintain. This can cause bugs and slow down development. Using coordinators makes the app easier to understand, test, and change, improving the user experience and developer productivity.
Where it fits
Before learning this, you should understand basic iOS app structure, view controllers, and navigation controllers. After this, you can explore advanced app architecture patterns like MVVM or Combine integration with coordinators.
Mental Model
Core Idea
A coordinator is like a traffic controller that directs which screen to show next, keeping view controllers focused on their own content.
Think of it like...
Imagine a theater play where actors (view controllers) perform their roles but a stage manager (coordinator) decides when each actor comes on stage and where they move. The actors don’t worry about the timing or order, just their performance.
┌───────────────┐      ┌───────────────┐
│  Coordinator  │─────▶│ ViewController│
│ (Traffic Ctrl)│      │  (Actor)      │
└───────────────┘      └───────────────┘
       ▲                      ▲
       │                      │
       └───── Manages flow ───┘
Build-Up - 6 Steps
1
FoundationWhat is a Coordinator?
🤔
Concept: Introduces the coordinator as a separate object that handles navigation.
In iOS, navigation is often done inside view controllers, but this mixes UI and flow logic. A coordinator is a new object that takes over navigation tasks. It creates view controllers and decides which one to show next.
Result
You get a cleaner separation: view controllers focus on UI, coordinators focus on navigation.
Understanding that navigation can be separated from UI code helps keep each part simpler and easier to manage.
2
FoundationBasic Coordinator Structure
🤔
Concept: Shows the minimal code structure of a coordinator in Swift.
A coordinator usually has a start() method to launch its flow. It holds a reference to a navigation controller to push or present view controllers. Example: class Coordinator { let navigationController: UINavigationController init(navigationController: UINavigationController) { self.navigationController = navigationController } func start() { // Show first screen } }
Result
You can create a coordinator instance and call start() to begin navigation.
Knowing the coordinator owns navigationController clarifies how it controls screen transitions.
3
IntermediateHandling Multiple Flows
🤔Before reading on: do you think one coordinator can handle all app navigation or should there be many? Commit to your answer.
Concept: Explains splitting navigation into multiple coordinators for different app parts.
Large apps have many flows (login, main app, settings). Each flow can have its own coordinator. A parent coordinator manages child coordinators. This keeps each flow isolated and easier to maintain.
Result
Navigation is modular: you can add or remove flows without changing unrelated parts.
Understanding modular coordinators prevents tangled navigation code and supports app scalability.
4
IntermediateCommunication Between Coordinators
🤔Before reading on: do you think child coordinators directly change parent navigation or notify the parent? Commit to your answer.
Concept: Shows how coordinators communicate using delegation or callbacks instead of direct navigation changes.
Child coordinators inform parents when they finish or need to trigger another flow. Parents then decide what to do next. This avoids tight coupling and keeps navigation decisions centralized.
Result
Coordinators stay loosely connected, making the app easier to test and change.
Knowing to use delegation for communication avoids bugs from unexpected navigation changes.
5
AdvancedMemory Management in Coordinators
🤔Before reading on: do you think coordinators can cause memory leaks if not handled properly? Commit to your answer.
Concept: Discusses how strong references between coordinators and view controllers can cause leaks and how to avoid them.
Coordinators often hold strong references to navigation controllers and child coordinators. If not released, this causes memory leaks. Use weak references for delegates and remove child coordinators when done.
Result
The app uses memory efficiently and avoids crashes or slowdowns.
Understanding reference cycles in coordinators helps prevent hard-to-find bugs in real apps.
6
ExpertAdvanced Coordinator Patterns and Variations
🤔Before reading on: do you think all coordinators must use UINavigationController or can they work with other UI patterns? Commit to your answer.
Concept: Explores variations like using coordinators with SwiftUI, tab bars, or modal flows, and combining with other architectures.
Coordinators can manage any navigation style, not just push/pop. They can coordinate tab bar controllers, modals, or SwiftUI navigation stacks. They also integrate with MVVM or Combine for reactive flows.
Result
You can adapt the coordinator pattern to many app designs and technologies.
Knowing the pattern’s flexibility allows you to apply it beyond UIKit and build modern, maintainable apps.
Under the Hood
Coordinators work by owning navigation controllers and creating view controllers programmatically. They keep references to child coordinators to manage flow lifecycles. Communication uses delegation or closures to avoid tight coupling. Memory management relies on weak references to prevent retain cycles.
Why designed this way?
The pattern was created to solve the problem of massive view controllers overloaded with navigation code. Separating navigation into coordinators improves code clarity and testability. Alternatives like putting navigation in app delegates or view controllers were too messy or inflexible.
┌───────────────┐       ┌─────────────────────┐
│ Parent Coord  │──────▶│ Child Coord (Login) │
│ (Manages flow)│       └─────────────────────┘
│               │
│  ┌───────────────┐  │       ┌─────────────────────┐
│  │ UINavigationController │
│  └───────────────┘  │──────▶│ ViewController (Login)│
└───────────────┘       └─────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do coordinators replace view controllers entirely? Commit to yes or no.
Common Belief:Coordinators replace view controllers and handle UI too.
Tap to reveal reality
Reality:Coordinators only handle navigation and flow, not UI or business logic inside view controllers.
Why it matters:Mixing UI code into coordinators defeats their purpose and leads to messy code.
Quick: Is it okay for coordinators to hold strong references to their child coordinators forever? Commit to yes or no.
Common Belief:Keeping strong references to child coordinators forever is fine.
Tap to reveal reality
Reality:Child coordinators must be removed when done to avoid memory leaks.
Why it matters:Not cleaning up coordinators causes memory bloat and app crashes.
Quick: Can a single coordinator handle all navigation in a large app easily? Commit to yes or no.
Common Belief:One coordinator can manage all navigation in any app size.
Tap to reveal reality
Reality:Large apps need multiple coordinators to keep navigation modular and manageable.
Why it matters:Using one coordinator for everything leads to complex, hard-to-maintain code.
Quick: Do coordinators always require UINavigationController? Commit to yes or no.
Common Belief:Coordinators must use UINavigationController to work.
Tap to reveal reality
Reality:Coordinators can manage any navigation style, including modals, tab bars, or SwiftUI navigation.
Why it matters:Believing this limits the pattern’s flexibility and modern usage.
Expert Zone
1
Coordinators often implement a 'finish' callback to notify parents when their flow ends, enabling clean removal and transition.
2
Using protocols for coordinator interfaces allows swapping implementations easily, supporting testing and modularity.
3
Combining coordinators with reactive frameworks like Combine or RxSwift can simplify asynchronous navigation flows.
When NOT to use
Avoid using coordinators for very simple apps with only one or two screens where adding them adds unnecessary complexity. Instead, use direct navigation in view controllers. Also, if your app heavily relies on declarative UI frameworks like SwiftUI, consider using native navigation tools before adding coordinators.
Production Patterns
In real apps, coordinators are often combined with MVVM architecture, where view models handle data and coordinators handle navigation. Parent coordinators manage child coordinators for features like onboarding, authentication, and main app flows. Coordinators also handle deep linking by interpreting URLs and starting the correct flow.
Connections
Model-View-ViewModel (MVVM)
Builds-on
Understanding coordinators helps separate navigation from UI and business logic, which MVVM also aims to separate, making the app more modular and testable.
Event-driven Architecture
Same pattern
Coordinators use delegation and callbacks to communicate, similar to event-driven systems where components react to events without tight coupling.
Air Traffic Control Systems
Analogy-based inspiration
Just like air traffic controllers manage plane movements safely and efficiently, coordinators manage screen transitions to keep app flow smooth and organized.
Common Pitfalls
#1Putting navigation code inside view controllers instead of coordinators.
Wrong approach:class ViewController: UIViewController { func goToNext() { let nextVC = NextViewController() navigationController?.pushViewController(nextVC, animated: true) } }
Correct approach:class Coordinator { let navigationController: UINavigationController func start() { let vc = ViewController() vc.onNext = { [weak self] in self?.showNext() } navigationController.pushViewController(vc, animated: true) } func showNext() { let nextVC = NextViewController() navigationController.pushViewController(nextVC, animated: true) } }
Root cause:Confusing navigation responsibility leads to bloated view controllers and tangled code.
#2Not removing child coordinators after their flow finishes.
Wrong approach:class ParentCoordinator { var childCoordinators = [Coordinator]() func startChild() { let child = ChildCoordinator() childCoordinators.append(child) child.start() } // No removal of child }
Correct approach:class ParentCoordinator { var childCoordinators = [Coordinator]() func startChild() { let child = ChildCoordinator() childCoordinators.append(child) child.onFinish = { [weak self, weak child] in if let child = child { self?.childCoordinators.removeAll { $0 === child } } } child.start() } }
Root cause:Forgetting to clean up child coordinators causes memory leaks and unexpected behavior.
#3Strong reference cycles between coordinators and view controllers.
Wrong approach:class Coordinator { var navigationController: UINavigationController func start() { let vc = ViewController() vc.delegate = self // strong reference navigationController.pushViewController(vc, animated: true) } }
Correct approach:class Coordinator { var navigationController: UINavigationController func start() { let vc = ViewController() vc.delegate = self // weak reference in ViewController navigationController.pushViewController(vc, animated: true) } } class ViewController: UIViewController { weak var delegate: Coordinator? }
Root cause:Not using weak references in delegates causes retain cycles and memory leaks.
Key Takeaways
The Coordinator pattern separates navigation logic from view controllers, making code cleaner and easier to maintain.
Using multiple coordinators for different app flows keeps navigation modular and scalable.
Communication between coordinators should use delegation or callbacks to avoid tight coupling.
Proper memory management with weak references and removing child coordinators prevents leaks.
The pattern is flexible and can be adapted to various navigation styles and architectures.