Robert J. Clegg
Robert J. Clegg

Reputation: 7360

NavigationStack - Pushing root View twice not working

Perhaps I am not understanding NavigationStack well.

I've setup a small example to demonstrate the issue:

@main
struct StacksApp: App {
    var body: some Scene {
        WindowGroup {
            ViewA(someText: "Root View")
        }
    }
}

ViewA (AKA Root View)

struct ViewA: View {
    private let someText: String?
    private let someNumber: Int?

    @State private var navigationPath = NavigationPath()

    init(someText: String? = nil,
         someNumber: Int? = nil) {
        self.someText = someText
        self.someNumber = someNumber
    }

    var body: some View {
        NavigationStack(path: $navigationPath) {
            VStack(spacing: 10) {
                if let someText {
                    Text("Showing \(someText) text")
                } else if let someNumber {
                    Text("Showing \(someNumber) number")
                }

                NavigationLink("Show View B", value: "ViewB")
            }
            .navigationDestination(for: String.self) { value in
                if value == "Hello" {
                    ViewA(someText: value)
                } else {
                    ViewB(path: $navigationPath)
                }
            }
            .navigationDestination(for: Int.self) { value in
                ViewA(someNumber: value)
            }
        }
    }
}

ViewB

struct ViewB: View {
    @Binding var path: NavigationPath

    var body: some View {
        VStack(spacing: 10) {
            Text("Hello, World!, this is View B!")
            NavigationLink("Go to ViewA - Showing Int value", value: 4)
            NavigationLink("Go to viewA Showing text Value", value: "Hello")
        }
        .navigationDestination(for: Int.self) { value in
            ViewA(someNumber: value)
        }
    }
}

The Problem

From ViewB I am unable to go to ViewA with different data - eg an Int. When I tap on "Go to ViewA - Showing Int value" or "Go to ViewA - Showing text value"

What happens is a new View is pushed on to the stack and then it pops me back to the original ViewA: ViewA(someText: "Root View")

If I were to replace ViewA with some other view that I was to show multiple times, with different data - eg ViewB - there is no issue and it works as expected.

Is this by design? Eg pushing a root view multiple times not being allowed?

Upvotes: 0

Views: 192

Answers (1)

Sweeper
Sweeper

Reputation: 271410

The problem is that the NavigationStack is part of ViewA. When you navigate to another ViewA, you get a new NavigationStack that is nested in the existing one. This causes the problems you described

ViewA should not contain a NavigationStack - it should just be the view you put inside the NavigationStack { ... }.

struct ViewA: View {
    let someText: String?
    let someNumber: Int?
    
    @Binding var navigationPath: NavigationPath
    
    init(someText: String? = nil, someNumber: Int? = nil, navigationPath: Binding<NavigationPath>) {
        self.someText = someText
        self.someNumber = someNumber
        self._navigationPath = navigationPath
    }
    
    var body: some View {
        VStack(spacing: 10) {
            if let someText {
                Text("Showing \(someText) text")
            } else if let someNumber {
                Text("Showing \(someNumber) number")
            }
            
            NavigationLink("Show View B", value: "ViewB")
        }
    }
}

The NavigationStack should go in another view. Note that the navigation destinations should also go here, otherwise you would end up having multiple navigation destinations for the same type, when ViewA is pushed for the second time.

struct ContentView: View {
    @State var navigationPath = NavigationPath()
    var body: some View {
        NavigationStack(path: $navigationPath) {
            ViewA(someText: "Foo", navigationPath: $navigationPath)
                .navigationDestination(for: String.self) { value in
                    if value == "Hello" {
                        ViewA(someText: value, navigationPath: $navigationPath)
                    } else {
                        ViewB(path: $navigationPath)
                    }
                }
                .navigationDestination(for: Int.self) { value in
                    ViewA(someNumber: value, navigationPath: $navigationPath)
                }
        }
    }
}
@main
struct StacksApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Upvotes: 1

Related Questions