How to use NavigationPath for routing in SwiftUI
Learn how to manage navigation in SwiftUI programmatically.
09 Jun 2024 · 5 min read
With NavigationPath, SwiftUI provides a robust and flexible way to handle navigation and routing. It allows us to programmatically control the navigation stack without the need to use a NavigationLink.
In this article, we'll explore how to use NavigationPath in a practical example.
Let's jump in.

When creating a NavigationStack, we have the possibility to pass in a path parameter:
struct SomeView: View {@State private var navigationPath = NavigationPath()var body: some View {NavigationStack(path: $navigationPath) {...}}}
The passed in path is a Binding to the navigation state for this stack - enabling us to control the navigation stack by appending data to it:
navigationPath.append(route)
Let's say, we are building an app which can navigate to a recipes search - which in turn can navigate to a recipe detail when a user selects one of the results.
The approach to control this flow could look as follows:
1. Defining an enum which represents all possible navigation routes
enum Route: Hashable {case recipesSearchcase recipeDetail(recipe: Recipe)}
We don't explicitly need it to use NavigationPath, but I personally find it really helpful to have explicitly defined navigation routes.
2. Creating an Observable router which holds the navigation path
To be able to access the same navigation path from different views, we create a router as an Observable which holds the reference to the navigation path and provides a utility function for navigation:
@Observableclass Router {var navigationPath = NavigationPath()func navigateTo(route: Route) {navigationPath.append(route)}}
3. Passing in the router as an environment object to all subviews
For a subview to be able to access and use our created router, we pass it in as an environment object:
struct MainView: View {@State private var router = RecipesRouter()var body: some View {NavigationStack(path: $router.navigationPath) {RecipesFeedView().environment(router)}}}
4. Defining a navigation destination for possible routes
As a next step, we define a navigationDestination for each of our routes by providing views a specific route should navigate to:
RecipesFeedView().environment(router).navigationDestination(for: RecipesRouter.Route.self) { route inswitch route {case .recipesSearch:RecipesSearchView().environment(router)case .recipeDetail(let recipe):RecipeDetailView(recipe: recipe).environment(router)}}
5. Using router where needed
Finally, we can use the router for navigation anywhere we need by accessing it tough the @Environment property wrapper:
struct RecipesFeedView: View {@Environment(RecipesRouter.self) var routervar body: some View {...Button(action: {router.navigateTo(route: .recipesSearch)}, label: {Image(systemName: "magnifyingglass")})}}



Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips



