I've seen several posts regarding NavigationStack in a NavigationSplitView. All had specific issues and some were marked as resolved. I couldn't get any of the suggested solutions working on macOS so I'll present some stripped down examples, all part of FB11842563:
1. Sidebar Selection
struct SidebarSelection: View {
@State var selection: Int?
@State var path: [Int] = []
var body: some View {
NavigationSplitView {
List(1...20, id: \.self, selection: $selection) { number in
Text("I like \(number)")
}
} detail: {
NavigationStack(path: $path) {
VStack {
Image(systemName: "x.squareroot")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("This is the NavigationStack root")
}
.padding()
.navigationDestination(for: Int.self) { number in
Text("You chose \(number)")
}
}
}
.onChange(of: selection) { newValue in
print("You clicked \(newValue)")
if let newValue {
path.append(newValue)
}
}
.onChange(of: path) { newValue in
print("Path changed to \(path)")
}
}
}
If we run this and click:
- „I like 5“
- „I like 6“
- „I like 7“
We would expect the detail view to show:
- „You chose 5“
- „You chose 6“
- „You chose 7“
And the console to show:
You clicked Optional(5)
Path changed to [5]
You clicked Optional(6)
Path changed to [5, 6]
You clicked Optional(7)
Path changed to [5, 6, 7]
What we actually see in the detail view is:
- „You chose 5“
- „This is the NavigationStack root“
- „You chose 7“
And the console shows:
Update NavigationRequestObserver tried to update multiple times per frame.
You clicked Optional(5)
Path changed to [5]
You clicked Optional(6)
Path changed to []
You clicked Optional(7)
Path changed to [7]
2. Sidebar and Stack Selection
Now we copy the list to the navigationDestination
:
struct SidebarAndStackSelection: View {
@State var selection: Int?
@State var path: [Int] = []
var body: some View {
NavigationSplitView {
List(1...20, id: \.self, selection: $selection) { number in
Text("I like \(number)")
}
} detail: {
NavigationStack(path: $path) {
VStack {
Image(systemName: "x.squareroot")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("This is the NavigationStack root")
}
.padding()
.navigationDestination(for: Int.self) { number in
VStack {
Text("You chose \(number)")
List(1...20, id: \.self, selection: $selection) { number in
Text("I like \(number)")
}
}
}
}
}
.onChange(of: selection) { newValue in
print("You clicked \(newValue)")
if let newValue {
path.append(newValue)
}
}
.onChange(of: path) { newValue in
print("Path changed to \(path)")
}
}
}
We repeat our test from above, clicking either on the sidebar or in the detail view and we expect the same outcome. This time the detail view shows the expected screen and the path is not completely wiped out but it is also not appended:
Update NavigationRequestObserver tried to update multiple times per frame.
You clicked Optional(5)
Path changed to [5]
You clicked Optional(6)
Path changed to [6]
You clicked Optional(7)
Path changed to [7]
3. Sidebar and Stack Selection, initialized
Same as before, but now we initialize the view with a non-empty path:
SidebarAndStackSelection(path: [1])
The app freezes on launch, CPU is at 100 percent and the console shows only:
Update NavigationRequestObserver tried to update multiple times per frame.
Update NavigationRequestObserver tried to update multiple times per frame.
The SwiftUI instruments seem to show heavy activity of the Stack and the SplitView:
4. Selection only in Stack
Once we remove the selection from the sidebar everything works as expected (adding the NavigationStack to the root view to be able to click on a number):
struct SidebarWithoutSelectionButStack: View {
@State var selection: Int?
@State var path: [Int] = []
var body: some View {
NavigationSplitView {
List(1...20, id: \.self) { number in
Text("I like \(number)")
}
} detail: {
NavigationStack(path: $path) {
List(1...20, id: \.self, selection: $selection) { number in
Text("I like \(number)")
}
.padding()
.navigationDestination(for: Int.self) { number in
VStack {
Text("You chose \(number)")
List(1...20, id: \.self, selection: $selection) { number in
Text("I like \(number)")
}
}
}
}
}
.onChange(of: selection) { newValue in
print("You clicked \(newValue)")
if let newValue {
path.append(newValue)
}
}
.onChange(of: path) { newValue in
print("Path changed to \(path)")
}
}
}
Problem of course is, that now the sidebar is useless.