Reputation: 171
I have a typical NavigationSplitView
layout in my app:
NavigationSplitView {
List(0..<3, selection: $selection) { item in
Label(MainDataType.data[item].name, systemImage: MainDataType.data[item].icon)
}
} content: {
switch selection {
case 0: StudentsView()
case 1: WorksView()
// More options here...
}
} detail: {
NoDataView() // <- Just for main view first presentation.
}
Sidebar
and Content
views work perfectly fine. For Detail
view, I overwrite default NoDataView
with, for example, StudentDetail
, from StudentsView
selection:
struct StudentsView() {
...
List(students, id:\.self) { student in
NavigationLink(destination: NavigationStack { // <- In order to get navigation inside SplitView detail column.
StudentDetailView(student: student)}) {
StudentCellView(student: student)
...
As in StudentDetailView
I have deeper NavigationLinks
(see attached gif).
This layout has been working until iOS17. Now it still works almost in every case, but this console message appears:
A NavigationLink is presenting a NavigationStack onto a column. This construction is not supported. Consider instead just presenting a new root view which will replace that of an existing stack in the column, or else the column itself if there is no NavigationStack in the target column. If there is a stack in the target column, appending to that stack's path is another option.
I'm testing these navigation changes:
struct StudentsView() {
...
List(students, id:\.self) { student in
NavigationLink(destination: StudentDetailView(student: student)) {
StudentCellView(student: student)
...
This way no console message is shown, and deep NavigationLinks
work (more or less), but with no navigation behavior (navigating back, animation...).
So I'm now struggling with this two questions:
NavigationStack
inside detail view of NavigationSplitView
?Upvotes: 5
Views: 4292
Reputation: 171
Voila! I have found the problem. So simple: just embedding detail:
column content itself in a NavigationStack
.
NavigationSplitView {
List(0..<3, selection: $selection) { item in
Label(MainDataType.data[item].name, systemImage: MainDataType.data[item].icon)
}
} content: {
switch selection {
case 0: StudentsView()
case 1: WorksView()
// More options here...
}
} detail: {
NavigationStack { // <- Here!
NoDataView()
}
}
Despite detail:
content is being in fact defined on intermediate views, this way general NavigationSplitView
outline seems to keep its behavior app-wide. So does every stack of views displayed on detail:
column.
Upvotes: 2
Reputation: 885
Your example looks a little like the three-column example from Apple.
In general, you would want to have your NavigationStack
as the root view in the details
part of the split view. Inside the NavigationStack
, you would then choose which view should be displayed, based on what has been selected in the content
part. This is exactly what the part in the error message means:
Consider instead just presenting a new root view which will replace that of an existing stack in the column
Here is a minimal example of what I mean:
struct TestView: View {
enum ContentSelection: Hashable {
case one
case two
}
@State var contentSelection: ContentSelection?
@State var columnVisibility: NavigationSplitViewVisibility = .doubleColumn
var body: some View {
NavigationSplitView(columnVisibility: $columnVisibility) {
List {
Text("Sidebar one")
}
} content: {
List(selection: $contentSelection) {
Text("Content one")
.tag(ContentSelection.one)
Text("Content two")
.tag(ContentSelection.two)
}
} detail: {
NavigationStack {
switch contentSelection {
case .one:
NavigationLink("ONE") {
Text("ONE one level deeper")
}
case .two:
NavigationLink("TWO") {
Text("TWO one level deeper")
}
case .none:
Text("Nothing")
}
}
}
}
}
In your case, you would have your students list in the content
part, but you would need to replace the NavigationLink
s in the list by using the List(selection:)
initializer. Based on this selection, you can display the correct student details in the details
section (inside the NavigationStack
there).
Also, for deeper navigation hierarchies, you can take a look at NavigationStack(path:root:), which works quite nicely together with the NavigationLink(value:label:)
and the .navigationDestination(for:destination:)
modifier. You can just create your own Destination
enum (make it conform to Hashable
) and use an array ([Destination]
) as your path
.
Upvotes: 7