Jordan Mann
Jordan Mann

Reputation: 477

SwiftUI - Make toolbar's NavigationLink use detail view

I'm working on a two-pane SwiftUI app with a sidebar and detail pane in a DoubleColumnNavigationView.

I would like to open a NavigationLink from the toolbar of the sidebar into the detail pane, as seen in "open from sidebar" in the gif below).

However, the view opens as a stack instead, as seen in "open from toolbar" in the gif below.

Using isDetailLink(true) appears to have no effect.

Demo of the issue in question, showing detail view opening in toolbar instead of sidebar.

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: Text("Opened from sidebar")) {
                    Text("Open from sidebar")
                }
            }.listStyle(SidebarListStyle())
            .navigationTitle("Sidebar")
            .toolbar {
                ToolbarItem {
                    // This should open in detail pane
                    NavigationLink(destination: Text("Opened from toolbar")) {
                        Text("Open from toolbar")
                    }
                }
            }
            Text("Detail pane")
        }.navigationViewStyle(DoubleColumnNavigationViewStyle())
    }
}

Upvotes: 3

Views: 2259

Answers (1)

Denis
Denis

Reputation: 3353

Universal solution

As you might know, NavigationLink doesn't work well when placed in a toolbar. What is suggested here - is to place a Button into a toolbar and use a hidden NavigationLink somewhere in the code. The button tells the link to open the detail view and the link does the action. Here is your code adjusted with this suggestion:

struct ContentView: View {
    /// A state that tracks whether the link in the toolbar should be opened
    @State var toolbarLinkSelected = false
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: Text("Opened from sidebar")) {
                    Text("Open from sidebar")
                }
            }.listStyle(SidebarListStyle())
            .navigationTitle("Sidebar")
            .toolbar {
                ToolbarItem {
                    Button(action: { toolbarLinkSelected = true }) {
                        Text("Open from toolbar")
                    }
                }
            }
            .background(
                NavigationLink(
                    destination: Text("Opened from toolbar"),
                    isActive: $toolbarLinkSelected
                ) {
                    EmptyView()
                }.hidden() // The link is not visible to user
            )
            Text("Detail pane")
        }.navigationViewStyle(DoubleColumnNavigationViewStyle())
    }
}

iOS / iPadOS only solution

Also, if you only need to support iOS and iPadOS, you can use an older way of implementing it using navigationBarItems. This won't work on macOS as this modifier is not available but works well for iOS/iPadOS. Here is the example:

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: Text("Opened from sidebar")) {
                    Text("Open from sidebar")
                }
            }.listStyle(SidebarListStyle())
            .navigationTitle("Sidebar")
            .navigationBarItems(trailing: NavigationLink(destination: Text("Opened from toolbar")) {
                Text("Open from toolbar")
            })
            Text("Detail pane")
        }.navigationViewStyle(DoubleColumnNavigationViewStyle())
    }
}

Upvotes: 2

Related Questions