I've run into two issues using NavigationSplitView
with .navigationDestination
modifiers.
First, programmatic navigation using .navigationDestination(isPresented: destination)
does not seem to work when used in the content
column of a NavigationSplitView
It does, however, work from the sidebar / first column.
Second, it appears that we need to add redundant .navigationDestination
modifiers on iPhone and iPad to handle how NavigationSplitView
is adapted when used on iPhone. Specially, when it is collapsed into a NavigationStack
.
Also, when an iPad UI is collapsed to a NavigationStack
via Stage Manager, then expanded again, .navigationDestination
modifiers in the content
column seem to get lost, preventing the content
list from working going forward. This somewhat explains why dynamically adding .navigationDestination
s via the first column push isn't sufficient. However it results in a broken UI.
Is there one location we can place .navigationDestination
modifiers to work both on iPad and iPhone? For example, technically, there is a .navigationDestination
in the content column of the NavigationSplitView
once one of the top level options are selected. However it's not at the top level. And the .navigationDestination
isn't there by default. It's only "pushed" there at a later time.
This makes sense, if you take into account the underlying adaptation implementation of NavigationSplitView
under the hood, but the documentation doesn't seem to account for how the view hierarchy is adopted by the system.
See this minimal reproduction...
import SwiftUI
enum SubSection: Hashable {
case first
case second
case third
var title: String {
switch self {
case .first:
return "First"
case .second:
return "Second"
case .third:
return "Third"
}
}
}
struct ContentView: View {
@State var showDetail: Bool = false
@State var showList: Bool = false
var body: some View {
let _ = Self._printChanges()
NavigationSplitView {
List {
NavigationLink(value: "A") {
Text("Section A")
}
// This "pushes" to the content column
Button {
showList.toggle()
} label: {
HStack {
Text("Section B")
Spacer()
Image(systemName: showList ? "checkmark.circle.fill" : "checkmark.circle")
}
}
}
.navigationDestination(for: String.self, destination: { value in
ListView(sectionID: value, showDetail: $showDetail)
})
.navigationDestination(isPresented: $showList) {
ListView(sectionID: "B", showDetail: $showDetail)
}
.listStyle(.sidebar)
.navigationTitle("Sidebar")
} content: {
Text("Default")
// Destinations for iPad
// We cannot use the destinations present in the ListView?
// Must destinations be in the content column to push to
// the detail column?
.navigationDestination(for: SubSection.self, destination: { value in
Text("Detail: \(value.title)")
})
.navigationDestination(isPresented: $showDetail, destination: {
Text("Detail: \(showDetail ? "true" : "false")")
})
} detail: {
Text("Detail")
}
.onChange(of: showDetail) { newValue in
print("Show Detail: \(newValue)")
}
}
}
struct ListView: View {
var sectionID: String
@Binding var showDetail: Bool
var body: some View {
List {
// This does *not* "push" to the detail column!
Button {
showDetail.toggle()
} label: {
HStack {
Text("Toggle isPresented")
Spacer()
Image(systemName: showDetail ? "checkmark.circle.fill" : "checkmark.circle")
}
}
NavigationLink(value: SubSection.first) {
Text("\(SubSection.first.title)")
}
NavigationLink(value: SubSection.second) {
Text("\(SubSection.second.title)")
}
NavigationLink(value: SubSection.third) {
Text("\(SubSection.third.title)")
}
}
.listStyle(.insetGrouped)
.navigationTitle("Section: \(sectionID)")
// Redundent destinations for iPhone as destinations in
// the content column are not yet realized in the collaped
// StackView representation of the NavigationSplitView?
.navigationDestination(for: SubSection.self, destination: { value in
Text("Detail: \(value.title)")
})
.navigationDestination(isPresented: $showDetail, destination: {
Text("Detail: \(showDetail ? "true" : "false")")
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
From my comment below...