In my app I want a tab bar at the bottom and a bar at the top that contains some static information. For my current solution I’m wrapping the tab view in a NavigationStack
, but I understand that the tab view should always be the first in the view hierarchy. With my current solution I only have to define the top bar once and it is present for all tabs. How can I have the tab view be the top view in the view hierarchy without having to add a navigation stack and the bar to each tab?
My current solution:
struct ContentView: View {
var body: some View {
NavigationStack {
TabView {
Group {
FirstView()
.tabItem {
Label("One", systemImage: "1.circle")
}
SecondView()
.tabItem {
Label("Two", systemImage: "2.circle")
}
}
}
}
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Image(systemImage: "apple.logo"
}
ToolbarItem(placement: .topBarTrailing) {
Image(systemImage: "person.circle"
}
}
}
}
I’ve tried adding the toolbar to the Group
inside the tab view instead, but that doesn’t work.
Edit
My solution was to move the .toolbar
modifier to the TabView
as suggested by @Raahs. Sadly I’ve found out why the NavigationStack
should be inside the TabView
and not the otter way around (as I have). For one of the views in the tab view I’ve added a search bar using the .searchable
modifier. When that view is loaded the search bar is added to the toolbar and ‘sticks’, when you go to a different tab in the tab view the search bar persists.
Removing the NavigationStack
around the TabView
and adding NavigationStack
s to each view in the tab view is the obvious and recommended suggestion. That would mean copying the .toolbar
modifier to each of those views (the toolbar relies on data fetched using a .task
modifier, that would need to be copied as well).
Is there a way to have all views in the tab view have their own NavigationStack
but use the same code to fill the toolbar?
2
Answers
If you move the
.toobar
to theTabView
like this:You’ll get a toolbar that stays when switching tabs. As far as I know you can’t have a toolbar without a
NavigationStack
.Is there a way to have all views in the tab view have their own NavigationStack but use the same code to fill the toolbar?
There sure is – you just need to make use of SwiftUI concepts and make things reusable.
You already know your approach is not the recommended way to go about it, not only because of the TabView inside a NavigationStack, but because the many challenges you will face down the road as far as navigation and flexibility goes.
When using multiple tabs and a common toolbar (or even without a common toolbar), you need to be able to control the tab a link should open in. As your app evolves, you will have links all over the place and without such flexibility you’ll hit roadblocks in no time.
I attached an example based on your use case. Typically this would be divided in separate files, but I kept it all together for easier copying.
Here are some key points:
With all this structure in place, everything becomes very easy.
For example, to add the toolbar to any view:
To add the searchable bar to any view (that has a toolbar), just add another modifier:
This allows you to have views that have a toolbar, or views without a toolbar, or views with a toolbar and a searchbar.
The searchbar modifier also has a placeholder for a
.task
modifier, since you mentioned that’s where you fetch data. It could also support multiple search bars with various data sources, by adding some parameters to the function and adapting the logic accordingly.So now you see that these view extension functions is one way of adding the same code to fill the toolbar.
Similarly, in order to have navigation support by adding the
.navigationDestination(for:
modifier to each tab’sNavigationStack
, we define the modifier in a view extension and then simply add it to the stack of each tab:If you copy and run the attached code, as you explore the app, you’ll notice:
Here’s the example code:
Although this may seem like a lot, I think it’s the most basic approach to having the navigation flexibility needed when using multiple tabs.
If you like this approach to Navigation, consider the Routing library, which does all this and then some by providing some useful functions for navigation. It’s what I am currently using, although I wish it was updated for the newer
@Observable
macro introduced in iOS 17.