A comprehensive iOS project demonstrating the Coordinator Pattern for navigation management in UIKit apps, including tab bar integration and child coordinators.
The Coordinator Pattern separates navigation logic from view controllers, making your code more:
- Testable - Navigation logic can be unit tested independently
- Reusable - View controllers don't know how they're presented
- Maintainable - All navigation logic is centralized in coordinators
- Flexible - Easy to change navigation flows without touching view controllers
CoordinatorExample/
├── Coordinators/
│ ├── MainCoordinator.swift # Main tab navigation coordinator
│ ├── LocationsCoordinator.swift # Locations tab coordinator
│ └── AccountCoordinator.swift # Child coordinator for account flow
├── ViewControllers/
│ ├── TabBarViewController.swift # Tab bar setup and coordinator management
│ ├── MainViewController.swift # Main tab content
│ ├── LocationsViewController.swift # Locations tab content
│ ├── AccountViewController.swift # Account screen (managed by child coordinator)
│ ├── DetailViewController.swift # Simple detail screen
│ └── BottomSheetViewController.swift # Example modal presentation
├── Models/
│ └── User.swift # Example data model
├── AppDelegate.swift
└── SceneDelegate.swift # App entry point
- Each tab has its own coordinator and navigation stack
- Independent navigation flows per tab
- Centralized tab bar configuration
- Complex flows managed by child coordinators
- Automatic memory management and cleanup
- Parent-child coordinator relationships
- Type-safe data injection through coordinators
- Dynamic titles based on data
- Proper dependency management
- Push navigation within tabs
- Modal presentation (bottom sheets)
- Child coordinator navigation
- Per-coordinator navigation bar styling
- Large titles support
- Custom appearance configurations
TabBarViewController
├── MainCoordinator (Tab 1)
│ ├── MainViewController
│ ├── DetailViewController
│ ├── BottomSheetViewController (modal)
│ └── AccountCoordinator (child)
│ └── AccountViewController
└── LocationsCoordinator (Tab 2)
└── LocationsViewController
// SceneDelegate creates the tab bar controller
window?.rootViewController = TabBarViewController()
// TabBarViewController sets up coordinators for each tab
class TabBarViewController: UITabBarController {
private var mainCoordinator: MainCoordinator?
private var locationsCoordinator: LocationsCoordinator?
override func viewDidLoad() {
let mainNavController = UINavigationController()
let locationNavController = UINavigationController()
mainCoordinator = MainCoordinator(navigationController: mainNavController)
locationsCoordinator = LocationsCoordinator(navigationController: locationNavController)
// Configure tab items and start coordinators
}
}
// Each tab has its own coordinator managing its navigation stack
class MainCoordinator: NSObject, Coordinator, UINavigationControllerDelegate {
func start() {
let vc = MainViewController()
vc.mainCoordinator = self
vc.title = "Main VC"
navigationController.pushViewController(vc, animated: false)
}
func showDetailViewController() {
let vc = DetailViewController()
vc.mainCoordinator = self
vc.title = "Some Detail VC"
navigationController.pushViewController(vc, animated: true)
}
}
// Complex flows use child coordinators
func showAccount() {
let currentUser = getCurrentUser()
let child = AccountCoordinator(navigationController: navigationController)
child.parentCoordinator = self
childCoordinators.append(child)
child.start(user: currentUser)
}
// Coordinators automatically clean up child coordinators when users navigate back
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
// Detect back navigation and cleanup child coordinators
if let accountViewController = fromViewController as? AccountViewController {
childDidFinish(accountViewController.accountCoordinator)
}
}
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController { get set }
}
- Sets up tab bar with navigation controllers
- Creates and manages top-level coordinators
- Configures tab bar items and appearance
- Manages navigation for the main tab
- Demonstrates push navigation, modal presentation, and child coordinators
- Custom navigation bar styling (white text on transparent background)
- Manages navigation for the locations tab
- Independent navigation stack from main tab
- Different navigation bar styling (black text)
- Child coordinator demonstrating data passing
- Takes a
User
object via dependency injection - Managed by parent coordinator lifecycle
func showDetailViewController() {
let vc = DetailViewController()
vc.mainCoordinator = self
vc.title = "Some Detail VC"
navigationController.pushViewController(vc, animated: true)
}
func presentSomeBottomSheet() {
let bottomSheetViewController = BottomSheetViewController()
bottomSheetViewController.mainCoordinator = self
bottomSheetViewController.modalPresentationStyle = .pageSheet
if let sheet = bottomSheetViewController.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.preferredCornerRadius = 16
sheet.prefersGrabberVisible = true
}
navigationController.present(bottomSheetViewController, animated: true)
}
func showAccount() {
let currentUser = getCurrentUser()
let child = AccountCoordinator(navigationController: navigationController)
child.parentCoordinator = self
childCoordinators.append(child)
child.start(user: currentUser)
}
// In AccountCoordinator
func start(user: User) {
let vc = AccountViewController(user: user)
vc.accountCoordinator = self
vc.title = user.name
navigationController.pushViewController(vc, animated: true)
}
The project demonstrates proper memory management through:
- Strong coordinator references in
TabBarViewController
- Weak coordinator references in view controllers
- Automatic child coordinator cleanup when navigating back
- Parent-child relationships preventing retain cycles
// TabBarViewController keeps coordinators alive
private var mainCoordinator: MainCoordinator?
private var locationsCoordinator: LocationsCoordinator?
// View controllers hold weak references
weak var mainCoordinator: MainCoordinator?
weak var accountCoordinator: AccountCoordinator?
struct User: Codable, Identifiable {
private(set) var id = UUID()
let name: String
let email: String
let age: Int
}
Used to demonstrate type-safe data passing between coordinators and dependency injection into view controllers.
- ✅ Tab-specific navigation stacks - Each tab maintains independent navigation
- ✅ Coordinator ownership - Tab bar controller owns top-level coordinators
- ✅ Data flows through coordinators - No direct view controller communication
- ✅ Centralized navigation configuration - All navigation logic in coordinators
- ✅ Automatic memory management - Proper cleanup prevents memory leaks
- ✅ Consistent patterns - Same approach for push, present, and child coordinators
- Open
CoordinatorExample.xcodeproj
in Xcode - Select your target device/simulator
- Press
Cmd + R
to run
The app will launch with a tab bar containing:
- Main Tab: Demonstrates push navigation, modals, and child coordinators
- Locations Tab: Independent navigation stack with different styling
- iOS 15.0+
- Xcode 14.0+
- Swift 5.0+
MainViewController
├── DetailViewController (push)
├── BottomSheetViewController (modal)
└── AccountCoordinator
└── AccountViewController (with User data)
LocationsViewController
└── (Additional location-specific navigation can be added)
Traditional Approach | Coordinator Pattern |
---|---|
Navigation scattered across VCs | Navigation centralized in coordinators |
Tight coupling between screens | Loose coupling through coordinator interface |
Hard to test navigation flows | Easy to unit test coordinators |
Difficult to change app flow | Easy to modify flows in coordinators |
Memory management issues | Automatic coordinator cleanup |
Mixed responsibilities | Clear separation of concerns |
This project can be extended with:
- More complex tab flows
- Deep linking support
- Navigation state restoration
- Coordinator-based authentication flows
- Custom transition animations
- Universal link handling
This project serves as a comprehensive foundation for implementing the Coordinator Pattern in production iOS apps with tab bar navigation.