I have a Split View with the sidebar, content, and detail. Everything is working, but when I select on a NavigationLink in my detail view, the back button is seen next to the title above the content view. I want that back button to be displayed in the top bar on the left side of the detail view. I was expecting it to do this automatically, but I must be missing something.
This is where I want it to appear.
This is where it appears.
I made a simplified version of my code, because it would be too much to post but this has the same behavior.
struct TestView: View {
enum SidebarSelections {
case cycles
}
@State private var sidebarSelection: SidebarSelections = .cycles
var body: some View {
NavigationSplitView(sidebar: {
List(selection: $sidebarSelection, content: {
Label("Cycles", systemImage: "calendar")
.tag(SidebarSelections.cycles)
})
}, content: {
switch sidebarSelection {
case .cycles:
NavigationStack {
List {
// Displayed in Content
NavigationLink("Cycle link", destination: {
// Displayed in the Detail
NavigationStack {
List {
NavigationLink("Detail Link", destination: {
Text("More details")
})
}
}
})
}
}
}
}, detail: {
ContentUnavailableView("Nothing to see here", systemImage: "cloud")
})
}
}
Is what I want to do possible? Here is a Stack Overflow post that had it working at one point.
I figured out a way to do what I want. To summarize it:
- Hide the navigation bar back button
- Create a toolbar item to work as the new back button
- Have it's action close your view
You can't use dismiss because it will get rid of whatever view is in the detail section of your NavigationSplitView. Instead, you must pass the variable being used to present the new view, and then set it back to false.
Lastly, your view that's equivalent to Content() needs a primaryAction toolbar item. Otherwise, the placement of toolbar items won't appear right, because it will be based on the entire toolbar and not the portion above Detail().
struct Sidebar: View {
var body: some View {
NavigationLink("Sidebar button", destination: {
Content()
})
}
}
struct Content: View {
var body: some View {
NavigationLink("Content button", destination: {
Detail()
})
.toolbar {
ToolbarItem(placement: .primaryAction, content: {
Button("Add") {
}
})
}
}
}
struct Detail: View {
@State private var showOtherView = false
var body: some View {
NavigationStack {
Button("Other view") {
showOtherView.toggle()
}
.navigationDestination(isPresented: $showOtherView, destination: {
OtherView(presenting: $showOtherView)
})
}
}
}
struct OtherView: View {
@Binding var presenting: Bool
var body: some View {
Text("More details here")
.navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .destructiveAction, content: {
Button(action: {
presenting = false
}, label: {
Image(systemName: "chevron.left")
.foregroundStyle(.secondary)
.font(.title2)
.frame(width: 20)
})
.buttonStyle(.automatic)
})
ToolbarItem(placement: .principal, content: {
Text("View Title")
.font(.title3)
})
}
}
}
#if os(macOS)
struct TestView: View {
var body: some View {
NavigationSplitView(sidebar: {
Sidebar()
}, content: {
}, detail: {
})
}
}
#endif
Here's a photo of what it looks like.
Using the standard behavior of the NavigationSplitView works fine, but I prefer the look and feel of this better.