Post

Replies

Boosts

Views

Activity

Best practices for accessing NavigationPath in child views
Hi all I'm reworking our app in SwiftUI. My ultimate goal is to access the NavigationPath from a child view which is used throughout different NavgationStacks. While searching for I came across different ways of achieving this. As I'm relatively new to SwiftUI it is hard to understand what the actual best practice seems to be. So for the use case. My app has a TabView and each Tab has its own NavigationStack which looks something like this struct TabNavigation: View { @State private var selectedProductType: StaticProductType = .all @StateObject private var appRouter = AppRouter() var body: some View { TabView(selection: $appRouter.selectedTab) { Overview(activeType: $selectedProductType) .tabItem { Label("Home", systemImage: "house") } .tag(Tab.home) AssortmentView(router: $appRouter.assortmentRouter, activeType: $selectedProductType) .tabItem { Label(String(localized: "assortment"), systemImage: "list.bullet") } .tag(Tab.assortment) } } The AssortmenView holds the NavigationStack and defines the routes. struct AssortmentView: View { @Binding var router: AssortmentRouter @Binding var activeType: StaticProductType var body: some View { NavigationStack(path: $router.navigationPath) { VStack { ProductTypeNavigation(activeType: $activeType) .padding(.top, 10) .padding(.horizontal, 10) Spacer() TabView(selection: $activeType) { ListNavigation(type: .all) .tag(StaticProductType.all) ListNavigation(type: .games) .tag(StaticProductType.games) ListNavigation(type: .digital) .tag(StaticProductType.digital) ListNavigation(type: .toys) .tag(StaticProductType.toys) ListNavigation(type: .movies) .tag(StaticProductType.movies) ListNavigation(type: .books) .tag(StaticProductType.books) } .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) } .addToolbar() .navigationDestination(for: AssortmentRouter.Route.self) { route in switch route { case .overview(let type): OverviewTypeView(type: type) case .productDetail(let productId): ProductDetailView(productId: productId) .environmentObject(router) case .productList: ProductList() } } } } } Through my app I often use a view to displaying products. This view is reused over different NavigationStacks. struct ProductDetailView: View { var productId: Int @StateObject private var viewModel: ProductDetailViewModel = ProductDetailViewModel() @State private var showErrorAlert = false @EnvironmentObject var router: AssortmentRouter var body: some View { VStack { if !viewModel.isRefreshing { let product = viewModel.product VStack { Text("Product: \(product.title)") NavigationLink(destination: ProductDetailView(productId: Product.preview.productId)) { Text("Test") } } .navigationTitle(product.title) } else { ProgressView() } }.task { await loadProduct() } .alert("Error", isPresented: $showErrorAlert, presenting: viewModel.localizedError) { _ in Button("Try again") { Task { await loadProduct() } } Button("Go Back", role: .cancel) { // access navigationPath } } message: { errorMessage in Text(errorMessage) } } @MainActor private func loadProduct() async { await viewModel.loadProduct(productId: productId) showErrorAlert = viewModel.localizedError != nil } } In this example I created an AppRouter which holds all information for the routes and some functions to accessing the NavigationPath. class AppRouter: ObservableObject { var assortmentRouter = AssortmentRouter() var selectedTab: Tab = .home func navigateTo(tab: Tab) { selectedTab = tab } } class AssortmentRouter: ObservableObject { var navigationPath = NavigationPath() enum Route: Hashable { case overview(type: StaticProductType) case productList case productDetail(productId: Int) } func navigateTo(route: Route) { navigationPath.append(route) } } This works fine as it is. The pro of this solution is that I don't have to pass the NavigationPath down each subview to use it as I can define it as EnvrionmentObject. The problem with this though, I like to reuse ProductDetailView also in my other NavigationStack which won't have a router binding of type AssortmentRouter as you can imagine. To come back to my initial question, what would be the best way to design this? Passing down a NavigationPath Binding and using different typing for navigationDestinaion values Define a callback which is passed as function parameter to the detail view Using dismiss, but I read that this is can lead to weird behaviour and bugs Any other option? Maybe changing the app architecture to handle this a better way Apolgize the long post, but I would be really glad to get some feedback on this, so I can do it the right way. Thank you very much
3
0
100
May ’25