Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to place global NavigationPath in SwiftUI

I'm currently developing an app using NavigationStack. I wonder where should I put the NavigationPath variable so I can modify it anywhere.

I tried to put it inside the root view as a State variable, but it seems difficult for me to modify it inside deeply-nested views.

struct RootView: View {
    @State var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) {
            // ...
        }
    }
}

struct SubView: View {
    var body: some View {
        Button("Push to root") {
            // how should I access `path` from here?
        }
    }
}

I also tried to put it as a global variable, but this may result that navigation are shared among all scene, which is not what I intended.

class Router: ObservableObject {
    static var shared = Router()
    @Published var path = NavigationPath()
}

struct RootView: View {
    @ObservedObject var router = Router.shared
    
    var body: some View {
        NavigationStack(path: $router.path) {
            // ...
        }
    }
}

struct SubView: View {
    @ObservedObject var router = Router.shared
    
    var body: some View {
        Button("Push to root") {
            router.path = []
        }
    }
}

I would appreciate if someone were to provide a workable design or any suggestions.

like image 750
wheatset Avatar asked Nov 22 '25 22:11

wheatset


2 Answers

Use a global router like below.

  1. Define a Router class which conforms to ObservableObject protocol and has a NavigationPath variable.
class Router: ObservableObject {
    @Published var path: NavigationPath = NavigationPath()

    static let shared: Router = Router()
}
  1. Update root view codes.
struct HomePage: View {
    @StateObject var router = Router.shared
    // 1. init router with Router.shared.
    @StateObject var router = Router.shared

    var body: some View {
        // 2. init NavigationStack with $router.path
        NavigationStack(path: $router.path) {
            Button {
                // 4. push new page
                Router.shared.path.append(category)
            } label: {
                Text("Hello world")
            }
            // 3. define navigation destinations
            .navigationDestination(for: Category.self, destination: { value in
                CategoryPage(category: value)
            })
            .navigationDestination(for: Product.self, destination: { value in
                ProductPage(product: value)
            })
    }

  1. Update child view codes.
struct CategoryPage: View {
    var body: some View {
        Button {
            // push new page with Router
            Router.shared.path.append(product)
        } label: {
            Text("Hello world")
        }
}
like image 192
Steven Avatar answered Nov 25 '25 12:11

Steven


You can use an Environment to pass some action downward:

final class Navigation: ObservableObject {
    @Published var path = NavigationPath()
}

extension EnvironmentValues {
    private struct NavigationKey: EnvironmentKey {
        static let defaultValue = Navigation()
    }

    var navigation: Navigation {
        get { self[NavigationKey.self] }
        set { self[NavigationKey.self] = newValue }
    }
}

Your rootview then:

struct RootView: View {
    @StateObject private var navigation = Navigation()

    var body: some View {
        NavigationStack(path: $navigation.path) {
        // ...
        }
        .environment(\.navigation, navigation)
    }
}

Your subview:

struct SubView: View {
    @Environment(\.navigation) private var navigation

    var body: some View {
        Button("Push to root") {
            navigation.path = NavigationPath() // i.e pop to root
        }
    }
}
like image 32
2pp Avatar answered Nov 25 '25 11:11

2pp