6

Following this question, how to add a toolbar divider for a three column view in swiftUI life cycle , I have a slightly different issue. I am trying to achive the same thing but the second and third columns are contained in a view which in turn is added inside a NavigationView next to first column which is the Sidebar.

Code example

import SwiftUI

@main
struct ThreeColumnsAppApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .frame(minWidth: 900, maxWidth: .infinity, minHeight: 600, maxHeight: .infinity)
        }
        .windowStyle(DefaultWindowStyle())
        .windowToolbarStyle(UnifiedWindowToolbarStyle(showsTitle: true))
    }
}

struct ContentView: View {
    var body: some View {
        
        NavigationView {
            Sidebar()
                .toolbar { Button(action: {}, label: { Image(systemName: "sidebar.left") }) }
            MainContentView()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

struct Sidebar: View {
    
    var body: some View {
        List {
            Text("Menu 1")
            Text("Menu 2")
            Text("Menu 3")
            Text("Menu 4")
        }
        .frame(minWidth: 250)
        .listStyle(SidebarListStyle())
    }
}

struct MainContentView: View {
    
    var body: some View {
        
        NavigationView {
            ListItemView()
            DetailView()
        }
        .navigationTitle("Items List")
        .navigationSubtitle("5 items found")
    }
}

struct ListItemView: View {
    
    var body: some View {
        
        List {
            Text("List item 1")
            Text("List item 2")
            Text("List item 3")
            Text("List item 4")
            Text("List item 5")
        }
        .frame(minWidth: 250)
        .listStyle(InsetListStyle())
        .toolbar {
            Button(action: {}, label: { Image(systemName: "arrow.up.arrow.down.square") })
        }
    }
}

struct DetailView: View {
    
    var body: some View {
        Text("Detail of list 1")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .toolbar {
                Button(action: {}, label: { Image(systemName: "plus") })
                Button(action: {}, label: { Image(systemName: "minus") })
                Button(action: {}, label: { Image(systemName: "pencil") })
            }
    }
}

Output

As you can see bellow, the arrow icon, which should be on the second column's toolbar, is pushed to the right together with the detail view's toolbar icons. It seems NavigationView sees ListItemView and DetailView toolbars as a single one.

Toolbar for three columns layout

Desired output

Expected alignment

Question

So, my question is how to have the toolbar icons aligned with their view?

horace1921
  • 73
  • 5

1 Answers1

7

In order to do this properly, you need a ToolbarItem on each of the three columns of the view. For example, this:

struct TripleNavView: View {
    
    var body: some View {
        NavigationView {
            
            Text("Column 1")
                .toolbar {
                    ToolbarItem {
                        Button(action: {}, label: {Image(systemName: "sidebar.left")})
                    }
                }
            Text("Column 2")
                .toolbar {
                    ToolbarItem {
                        Button(action: {}, label: {Image(systemName: "plus")})
                    }
                }
            Text("Column 3")
                .toolbar {
                    ToolbarItem {
                        Button(action: {}, label: {Image(systemName: "trash")})
                    }
                }
        }
        .navigationTitle("Title")
    }
}

Produces this:

3 Column Navigation View, with toolbar items on each column

The important thing is that both columns 2 and 3 need to have a ToolbarItem of some kind. If you only put a button on one of the columns, then SwiftUI will place it all the way at the trailing edge of the toolbar, and ruin the look.

Window with incorrect toolbar

As a result, if you don't want a button on a particular column, substitute a Spacer in place of the button.

3 Column Navigation view, with no toolbar item in column 2

Note that leaving it blank or using EmptyView() won't work - SwiftUI will optimize it out, and act as if you didn't include a ToolbarItem in the first place!

Michael Berk
  • 365
  • 2
  • 16
  • 1
    Thank you for your answer. I also found out that NavigationView needs to know the number of columns at compile time. In my example, NavigationView has 2 columns, Sidebar and MainContentView, while ListItemView and DetailView were contained inside MainContentView. For that reason, at compile time NavigationView sees only two columns and places the toolbar items accordingly. – horace1921 Aug 31 '21 at 05:54
  • Thanks @horace1921, this tripped me up so added a dummy .toolbar with a Spacer() on the initial view used in Column3 – thiezn Apr 29 '22 at 09:47