A powerful and lightweight Swift package for managing navigation in SwiftUI apps using an imperative, coordinator-based approach. Build complex navigation flows with clean, testable, and maintainable code.
๐ Read the full article on Medium
- ๐ฏ Imperative Navigation API - Control navigation programmatically from coordinators or view models
- ๐ฑ Multiple Presentation Styles - Support for push, sheet, and full-screen cover presentations
- ๐๏ธ Coordinator Pattern - Decouple navigation logic from views for better architecture
- โ Type-Safe - Compile-time safety for navigation parameters
- ๐งช Testable - Mock coordinators and test navigation flows easily
- ๐ State Management - Share state across navigation flows seamlessly
- ๐ฆ Lightweight - Minimal dependencies, just SwiftUI
While SwiftUI promotes declarative navigation with NavigationStack and NavigationLink, complex navigation flows can become challenging to manage. Imperative navigation offers:
- โ Separation of Concerns - Navigation logic lives outside of views
- โ Enhanced Testability - Test navigation flows independently from UI
- โ Complex Flows Made Simple - Handle multi-step processes, conditional navigation, and dynamic routing
- โ Single Source of Truth - Centralized navigation state management
- โ Better Code Organization - Clear responsibility boundaries between views and navigation
Add the package to your project using Xcode:
- File โ Add Package Dependencies
- Enter the repository URL:
https://github.com/ahmdmhasn/swiftui-imperative-navigation.git - Select the version and add to your target
Or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/ahmdmhasn/swiftui-imperative-navigation.git", from: "1.0.0")
]- iOS 16.0+ / watchOS 9.0+ / tvOS 16.0+ / visionOS 1.0+
- Xcode 16.0+
- Swift 6.0+
@MainActor
final class AppCoordinator {
let navigationController = NavigationController()
func showDetail(_ item: Item) {
navigationController.push(DetailView(item: item))
}
func showSettings() {
navigationController.sheet(SettingsView())
}
func showConfirmation() {
navigationController.present(ConfirmationView())
}
}@main
struct MyApp: App {
@StateObject private var coordinator = AppCoordinator()
var body: some Scene {
WindowGroup {
NavigationView(
controller: coordinator.navigationController,
root: {
HomeView(coordinator: coordinator)
}
)
}
}
}struct HomeView: View {
let coordinator: AppCoordinator
var body: some View {
VStack {
Button("Show Detail") {
coordinator.showDetail(selectedItem)
}
Button("Show Settings") {
coordinator.showSettings()
}
}
}
}The central controller for managing navigation:
@MainActor
public final class NavigationController: ObservableObject {
// Push a view onto the navigation stack
public func push<V: View>(_ view: V)
// Pop the top view from the stack
public func pop()
// Pop all views and return to root
public func popToRoot()
// Present a view as a full-screen cover
public func present<V: View>(_ view: V)
// Present a view as a sheet modal
public func sheet<V: View>(_ view: V)
// Dismiss the current modal (sheet or full-screen)
public func dismiss()
}Check out the comprehensive example app included in the repository. It demonstrates:
- ๐ E-commerce shopping flow with product catalog
- ๐ฆ Product details with reviews and ratings
- ๐๏ธ Shopping cart with sheet presentation
- ๐ณ Multi-step checkout process
- โ Order confirmation with full-screen cover
- ๐ Complex navigation flows and state management
The package includes comprehensive unit tests for the navigation controller. The coordinator pattern makes your navigation logic highly testable:
@MainActor
final class MockAppCoordinator: AppCoordinator {
var didShowDetail = false
var didShowSettings = false
override func showDetail(_ item: Item) {
didShowDetail = true
}
override func showSettings() {
didShowSettings = true
}
}
// Test your view models or coordinators
func testNavigation() {
let coordinator = MockCoordinator()
coordinator.showDetail(item)
XCTAssertTrue(coordinator.didShowDetail)
}Contributions are welcome! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please read our contribution guidelines before submitting a PR.
This project is licensed under the MIT License. See the LICENSE file for details.
Ahmed M. Hassan
- GitHub: @ahmdmhasn
- Medium: @ahmdmhasn
If you find this project helpful:
- โญ Star the repository
- ๐ฆ Share on social media
- ๐ Write about it
- ๐ฌ Provide feedback and suggestions
Thanks to the SwiftUI community for inspiration and feedback on navigation patterns.
