SwiftUI: Conditionally Showing NavigationItem and ToolbarItem

Toolbars and Navigation items got significant improvements from what they originally where SwiftUI 1.0 back in 2019.

Let’s imagine that you want a build an onboarding screen where the user lands when your app is launched for the first time ever. Typically you should show a series of screens or “onboarding steps” that the user can browse or skip entirely. So you will need to show four navigation item buttons:
- Previous: browses back to the previous onboarding step if any
- Next: proceeds to the next item if any.
- Close: closes the onboarding once the user has reached the last step
- Skip: closes the onboarding flow, it’s shown from step 1 to N-1

So if out first attempt would be to set up the Previous button on the leading side of the navigation bar. But this breaks

.toolbar {
if viewModel.showPreviousButton {
ToolbarItem(placement: .navigationBarLeading) {
Button("previous", action: viewModel.previous)
} // Closure containing control flow statement cannot be used with result builder 'ToolbarContentBuilder'
}
}

So I started googling, because there had to be a way to achieve this. Unsurprisingly, Peter Steinberger from PDFKit had already stumbled with this issue

As always, we are standing in the shoulders of giants. This article from Majid Jabrayilov shows us how to reuse and encapsulate toolbar and navigation bar item logic into ToolbarContent compliant structs. So I decided to give it a go and mix this two great resources.

var body: some View {
VStack {
/// the contents of your view
}
.toolbar {
ItemsToolbar(
next: viewModel.next,
previous: viewModel.previous,
skip: skip,
close: skip,
nextButton: viewModel.showRightBarButton,
showPrevious: viewModel.showPreviousButton
)
}

What’s ItemsToolbar doing? It basically returns a ToolbarContent Builder that will return the desired toolbar / navbar for us

struct ItemsToolbar: ToolbarContent {
let next: () -> Void
let previous: () -> Void
let skip: () -> Void
let close: () -> Void
ToolbarItemGroup(placement: .navigationBarTrailing) {
switch nextButton {
case .close:
Button(
"Close",
action: close
)
case .skip:
Button(
"Next",
action: next
)
Button(
"Skip",
action: skip
)
case .none:
EmptyView()
}
}
}
}

Since ToolbarItemGroup allows conditional in its builder block we can place and encapsulate the logic there.

Thanks For Reading!

Resources:
https://swiftwithmajid.com/2020/07/15/mastering-toolbars-in-swiftui/
https://mobile.twitter.com/steipete/status/1380821252563161089

Software Developer. Rookie surfer