Reputation: 699
I am looking for a way to achieve a Toolbar for a three column layout like Mail.app. Also the Notes.app uses almost the same Toolbar with the only important difference between the two apps being that Notes.app looks like it's WindowStyle
is a HiddenTitleBarWindowStyle
and Mail.app looks like a Default|TitleBarWindowStyle
.
Following should be true:
HiddenTitleBarWindowStyle
)
DefaultWindowStyle
with only a small Divider()
-like line in the Toolbar.
Which combination of WindowStyle
, WindowToolbarStyle
and .toolbar
configuration do I need to achieve this setup?
I noticed that it is not possible to remove the divider line that Notes.app shows. I have not found a references to any such element in the docs though.
I have distilled the problem down to a simple app with mostly toolbar content. In my original code I use two nested NavigationView
s, while for the example I used only one NavigationView
with two Lists. However the Toolbar
results are the same.
DefaultWindowStyle
import SwiftUI
@main
struct ToolbarTestApp: App {
var body: some Scene {
WindowGroup {
ContentView(titleBarIsHidden: true)
}
.windowToolbarStyle(UnifiedWindowToolbarStyle())
.commands {
SidebarCommands()
}
}
}
struct ContentView: View {
@State var destination: String = "Toolbar Test"
@State var detail: String = ""
var body: some View {
NavigationView {
List {
Button(action: {self.destination = "Item with the identifier: 1"}, label: {
Text("Item 1")
})
.buttonStyle(DefaultButtonStyle())
Button(action: {self.destination = "Item 2"}, label: {
Text("Item 2")
})
.buttonStyle(DefaultButtonStyle())
}
.listStyle(SidebarListStyle())
List {
NavigationLink(
destination: DetailView(text: "\(destination) – \(detail)").onAppear{self.detail = "Detail 1"},
label: {
Text("\(destination) – Detail 1")
})
NavigationLink(
destination: DetailView(text: "\(destination) – \(detail)").onAppear{self.detail = "Detail 2"},
label: {
Text("\(destination) – Detail 2")
})
}
.listStyle(InsetListStyle())
Text("\(destination) – \(detail)")
}
.navigationTitle(destination)
.navigationSubtitle(detail)
.toolbar(id: "nav") {
ToolbarItem(id: "plus", placement: ToolbarItemPlacement.principal, showsByDefault: true) {
HStack {
Button(action: {print("pressed")}, label: {
Image(systemName: "plus.circle")
})
}
}
ToolbarItem(id: "spacer", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
HStack {
Spacer()
}
}
ToolbarItem(id: "sidebar.end", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
Button(action: {print("pressed")}, label: {
Image(systemName: "sidebar.right")
})
}
}
}
}
This example will result in a Toolbar
that will never show the divider line that splits the whole Toolbar
in two parts. Also the first ToolbarItem
is positioned in the center of the Toolbar
. I tried all ToolbarItemPlacement
but none caused the item to move to the far left adjacent to the title.
HiddenTitleBarWindowStyle
@main
struct ToolbarTestApp: App {
var body: some Scene {
WindowGroup {
ContentViewForHiddenTitleBar()
}
.windowStyle(HiddenTitleBarWindowStyle()) // added hidden title style
.windowToolbarStyle(UnifiedWindowToolbarStyle())
.commands {
SidebarCommands()
}
}
}
struct ContentViewForHiddenTitleBar: View {
@State var destination: String = "Toolbar Test"
@State var detail: String = ""
var body: some View {
NavigationView {
List {
Button(action: {self.destination = "Item with the identifier: 1"}, label: {
Text("Item 1")
})
.buttonStyle(DefaultButtonStyle())
Button(action: {self.destination = "Item 2"}, label: {
Text("Item 2")
})
.buttonStyle(DefaultButtonStyle())
}
.listStyle(SidebarListStyle())
// add geometry reader to trim title width in toolbar
GeometryReader { geometry in
List {
NavigationLink(
destination: DetailView(text: "\(destination) – \(detail)").onAppear{self.detail = "Detail 1"},
label: {
Text("\(destination) – Detail 1")
})
NavigationLink(
destination: DetailView(text: "\(destination) – \(detail)").onAppear{self.detail = "Detail 2"},
label: {
Text("\(destination) – Detail 2")
})
}
// there is no title anymore so let's fake it.
.toolbar(id: "list navigation") {
ToolbarItem(id: "title", placement: ToolbarItemPlacement.navigation, showsByDefault: true) {
VStack(alignment: .leading) {
Text(destination)
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
Text(detail)
.font(.subheadline)
.opacity(0.6)
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(width: geometry.size.width)
}
}
}
.listStyle(InsetListStyle())
Text("\(destination) – \(detail)")
}
.navigationTitle(destination)
.navigationSubtitle(detail)
.toolbar(id: "nav") {
// primary action will place the item next to the divider line.
ToolbarItem(id: "plus", placement: ToolbarItemPlacement.primaryAction, showsByDefault: true) {
HStack {
Button(action: {print("pressed")}, label: {
Image(systemName: "plus.circle")
})
}
}
ToolbarItem(id: "spacer", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
HStack {
Spacer()
}
}
ToolbarItem(id: "sidebar.end", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
Button(action: {print("pressed")}, label: {
Image(systemName: "sidebar.right")
})
}
}
}
}
This example will result in a Toolbar
that will always show a full height divider. Even if the title is too long. Therefore a GeometryReader
was added. This is fine until the sidebar will collapse. The placement of ToolbarItems
will not be correct. Also when customising the Toolbar
there would be the possibility to remove the title, which should not be possible.
Upvotes: 5
Views: 2812
Reputation: 5105
The default title bar style is fine, you just need to attach your toolbar items to the subviews of your top NavigationView, e.g.:
var body: some View {
NavigationView {
List {
...
}
.listStyle(SidebarListStyle())
.toolbar {
ToolbarItem {
Button(action: { }, label: {
Image(systemName: "sidebar.right")
})
}
}
List {
...
}
.listStyle(InsetListStyle())
.toolbar {
ToolbarItem {
Button(action: { }, label: {
Image(systemName: "plus.circle")
})
}
}
Text("\(destination) – \(detail)")
}
.navigationTitle(destination)
.navigationSubtitle(detail)
}
I didn’t attach any toolbar items to the third column (the Text
) but you can — just be sure to attach the same toolbar items to your DetailViews, as its toolbar will replace the Text’s toolbar when the user navigates. (if you’re not sure what I mean, just try it and you’ll quickly see what I’m talking about :)
Upvotes: 3