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?
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()
}
}
}