SwiftUINavigationRouter is a lightweight protocol designed to simplify and structure navigation logic in SwiftUI using NavigationStack and NavigationPath. It provides a scalable and reusable way to manage navigation per feature or flow in your app.
- ๐ Decoupled navigation logic
- ๐ Type-safe and testable
- ๐ง Declarative API
- ๐ Supports
push,pop, andpopToRootout of the box - ๐งฑ Each flow can have its own router
- ๐ ๏ธ Ready for dependency injection
- ๐ฒ Example App Included
- ๐ฆ Future support for
.presentnavigation
In Xcode, open your project and go to:
File > Add Packages...
Then, enter the following URL:
https://github.com/juansanzone/swiftui-navigation-router
You can also add it manually to your Package.swift:
.package(url: "https://github.com/juansanzone/swiftui-navigation-router.git", from: "1.0.0")To use the router, youโll need to:
- Create a class that conforms to
SwiftUINavigationRouterProtocol - Define your
Destinationenum - Implement the
viewFor(for:)method - Connect everything inside a
NavigationStackin your root view
import SwiftUI
import SwiftUINavigationRouter
final class PaymentsRouter: SwiftUINavigationRouterProtocol {
@Published var navigationPath: NavigationPath = .init()
}
extension PaymentsRouter {
enum Destination: Hashable {
case detailView
case successView(paymentID: String)
}
}
extension PaymentsRouter {
@ViewBuilder
func viewFor(for destination: Destination) -> some View {
switch destination {
case .detailView:
DetailView()
case .successView(let paymentID):
Text("Success View: \(paymentID)")
}
}
}โ Each router manages the navigation logic of a specific flow (e.g., Payments, Profile, Onboarding, etc).
import SwiftUI
struct HomeView: View {
@StateObject private var router: PaymentsRouter = .init()
var body: some View {
NavigationStack(path: $router.navigationPath) {
VStack {
Button("Details") {
router.push(screen: .detailView)
}
}
.navigationDestination(for: PaymentsRouter.Destination.self) { destination in
router.viewFor(for: destination)
.environmentObject(router)
}
.navigationTitle("HomeView")
}
}
}
โ ๏ธ In this example, we use@EnvironmentObjectfor simplicity. In real-world apps, prefer a proper dependency injection strategy (e.g., via factory, container, or constructor injection).
import SwiftUI
struct DetailView: View {
@EnvironmentObject var router: PaymentsRouter
var body: some View {
VStack {
Text("DetailView")
.onTapGesture {
router.push(screen: .successView(paymentID: "123"))
}
}
}
}Pushes a new screen defined in your Destination enum:
router.push(screen: .detailView)Pushes a view directly to the NavigationPath:
router.push("AnyHashableView")Note: This method is less type-safe. Prefer using
.push(screen:)with an enum when possible.
Pops the last view in the stack:
router.pop()Clears the entire navigation stack and returns to the root:
router.popToRoot()Each major flow (e.g., Payments, Orders, Onboarding) should have its own router. This promotes separation of concerns and improves scalability and testability.
final class OnboardingRouter: SwiftUINavigationRouterProtocol { ... }
final class OrdersRouter: SwiftUINavigationRouterProtocol { ... }Bored and tired of repeatedly defining navigationDestination everywhere? Let NavigationRouterView handle everything for you!
- โ Simplified Setup: Define your navigation logic once and reuse it effortlessly across your app.
- โ
Cleaner Code: No more repetitive
navigationDestinationsetups cluttering your SwiftUI views. - โ Easy Maintenance: Centralized routing makes your navigation easy to maintain and scale.
NavigationRouterView uses a simple yet powerful protocol NavigationRouterProtocol, which lets you define navigation paths and corresponding views neatly in one place.
Simplify your view by embedding it within NavigationRouterView:
struct HomeView: View {
@StateObject private var router = MyRouter()
var body: some View {
NavigationRouterView(router: router) {
VStack {
Button("Push Detail View") {
router.push(screen: .detailView)
}
}
.navigationTitle("Home")
}
}
}That's it! ๐ Now your navigation is cleaner, modular, and easier than ever!
A basic working example is available inside the ExampleApp folder.
This mini app demonstrates how to:
- Create a router for a feature flow
- Push and pop views using the router
- Display views based on enum-driven navigation
You can open the folder with Xcode and run it on the simulator to see SwiftUINavigationRouter in action as a local package.
- Support for push-based navigation
- Built-in support for
popandpopToRoot - Sample app
- Automatic Injection: The router is automatically injected into your stack views using SwiftUI's EnvironmentObject.
- Full integration with dependency injection containers
-
presentanddismissAPI (coming soon)
Pull requests are welcome! Feel free to fork the repo, open issues, and help shape the future of SwiftUINavigationRouter.
MIT License
Created with โค๏ธ by Juan Sanzone