SwiftUI Toolbar Placement Cheatsheet
Reading time: 5 min
This recipe is a cheatsheet for various ToolbarItemPlacement
values and combinations on iOS. This is useful because:
- The namings of the placement values don't necessarily clearly depict where will a
ToolbarItem
end up. - Some values don't play well with each other. E.g,
.primaryAction
will hide.confirmationAction
if it's placed above it, but not if placed below. - Some positions change if
TitleDisplayMode
is.inline
.
First we'll quickly introduce the toolbar
modifier, and then we'll look at how do various item placements and their combinations look on iOS.
TL;DR Here are the links to the bottom bar summary and navigation bar summary.
Intro to Toolbars
iOS 14 introduced the toolbar
modifier, allowing you to add ToolbarItem
s to either the toolbar (bottom bar) or the navigation bar. Note that this only works for views that are embedded in a NavigationView (either directly, or become via the navigation stack).
Here's a simple example that adds a few buttons around:
Note: The example uses the blueNavigation
modifier from this recipe on Navigation Bar Styling.
struct ToolbarTestView: View {
var body: some View {
NavigationView { // 1
Text("Hello, World!")
.padding()
.navigationTitle("Toolbar Tester")
.blueNavigation
.toolbar { // 2
ToolbarItem(placement: .navigation) { // 3
Button("Navigation") { } // 4
}
ToolbarItem(placement: .bottomBar) { // 3
Button("BottomBar") { } // 4
}
}
}
}
}
What happens here is:
- Views that use
toolbar
must be embedded inNavigationView
. Alternatively, the view from which you navigated to viaNavigationLink
should be. - Use the same
toolbar
modifier for all items, regardless of if they're in the navigation bar or bottom bar. - Use the
ToolbarItem
wrapper to add items to the bar and specify the position withplacement:
. - Add a regular
Button
with a title and empty action.
The result looks like this. You can see two buttons, one added to the navigation bar and one to the bottom toolbar:
Okay, time to dive into all the placements and their combos!
ToolbarItemPlacement and its values
The ToolbarItemPlacement
is a struct with a bunch of static constants representing different toolbar placements (you can think of it as an enum, even though it's not implemented that way). Here are all the supported placement values:
automatic
principal
navigation
primaryAction
status
confirmationAction
cancellationAction
destructiveAction
navigationBarLeading
navigationBarTrailing
bottomBar
We'll look at the placements that affect the bottom space (bottom toolbar) and the top space (navigation bar) separately, as they don't affect each other.
Fot the sake of brevity, example will just show the code inside the toolbar
modifier block.
Bottom bar placements
The bottom bar placements are status
and bottomBar
.
ToolbarItem(placement: ToolbarItemPlacement.status) {
Button("Status") { }
}
ToolbarItem(placement: ToolbarItemPlacement.bottomBar) {
Button("BottomBar") { }
}
status
is placed in the center of the toolbar.bottomBar
starts from the leading edge of the toolbar.
If you add another bottomBar
to the list:
ToolbarItem(placement: ToolbarItemPlacement.bottomBar) {
Button("BottomBar2") { }
}
it goes to the trailing edge:
If you keep adding more bottomBar
items:
ToolbarItem(placement: ToolbarItemPlacement.bottomBar) {
Button("BottomBar3") { }
}
they'll keep lining up at the trailing edge:
Multiple status
items:
ToolbarItem(placement: ToolbarItemPlacement.status) {
Button("Status") { }
}
ToolbarItem(placement: ToolbarItemPlacement.status) {
Button("Status2") { }
}
are just sequentially lined up in the center (if possible):
Summary:
status
items(s) are placed in the center.- First
bottomBar
items goes to the leading edge. - Other
bottomBar
items go to the trailing edge. - The mix sort of works as you would expect.
Navigation bar placements
Things get a bit more complicated with the navigation bar placements, as there are many overlapping ones:
automatic
,primaryAction
,confirmationAction
,destructiveAction
andnavigationBarTrailing
all compete for the navigation bar trailing position.navigation
,cancellationAction
andnavigationBarLeading
compete for the navigation bar leading position.principal
is the only one placed in the center of the navigation bar.
Here's a quick code to illustrate where do these placements live:
ToolbarItem(placement: ToolbarItemPlacement.automatic) {
Button("Automatic") { }
}
ToolbarItem(placement: ToolbarItemPlacement.principal) {
Button("Principal") { }
}
ToolbarItem(placement: ToolbarItemPlacement.navigation) {
Button("Navigation") { }
}
Here's where things get more than a little fuzzy. If you line up all the trailing placement options, like this:
ToolbarItem(placement: ToolbarItemPlacement.automatic) {
Button("Automatic") { }
}
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) {
Button("NavigationBarTrailing") { }
}
ToolbarItem(placement: ToolbarItemPlacement.primaryAction) {
Button("PrimaryAction") { }
}
ToolbarItem(placement: ToolbarItemPlacement.confirmationAction) {
Button("ConfirmationAction") { }
}
ToolbarItem(placement: ToolbarItemPlacement.destructiveAction) {
Button("DestructiveAction") { }
}
you'll only get the automatic
one:
If you place the primaryAction
above navigation
, then "Primary" goes to top.
However, if you place navigationBarTrailing
above primaryAction
, you get something like this:
Moral of the story is don't mix competing modifiers together - pick one and stick with it. The combination of placements and their ordering (which item is placed above the other in code) both seem to matter and the results are just weird.
The same is true for leading placement options.
ToolbarItem(placement: ToolbarItemPlacement.navigation) {
Button("Navigation") { }
}
ToolbarItem(placement: ToolbarItemPlacement.cancellationAction) {
Button("CancellationAction") { }
}
ToolbarItem(placement: ToolbarItemPlacement.navigationBarLeading) {
Button("NavigationBarLeading") { }
}
navigation
on top eats up everything:
But if it goes to the bottom:
ToolbarItem(placement: ToolbarItemPlacement.cancellationAction) {
Button("CancellationAction") { }
}
ToolbarItem(placement: ToolbarItemPlacement.navigationBarLeading) {
Button("NavigationBarLeading") { }
}
ToolbarItem(placement: ToolbarItemPlacement.navigation) {
Button("Navigation") { }
}
you get all the buttons:
Also, note that having more than one navigation
, principal
or automatic
item has no effect - it always only displays the top one. Multiplying items works for other placement types.
For the last test, let's use the inline
navigation bar title. Change the navigationTitle
line to:
.navigationBarTitle("Toolbar Tester", displayMode: .inline)
With a navigation
and confirmationAction
items, it looks like this:
However, if you add a principal
, it covers up the title:
Summary:
- Trailing -
automatic
,primaryAction
,confirmationAction
,destructiveAction
andnavigationBarTrailing
. - Leading -
navigation
,cancellationAction
andnavigationBarLeading
. - Principal - center.
automatic
andnavigation
cover up all the other placements in equal positioning.automatic
,navigation
andprincipal
don't allow duplication, other placement options do.confirmationAction
has default bold text.- In the
inline
display mode,principal
replaces the title. - Generally, don't mix these placement options together. Pick one for leading/trailing and stick with it.