FYI, .navigationBarItems is deprecated. You should use the replacement instead.
Although I am not sure exactly of the logic/setup used in the Files app, it does seem to use a TabView with .sidebarAdaptable style. This modifier is new to iOS 18 so you'll need to work in a project supporting iOS 18+.
When using multiple tabs, it's best for each tab to have its own NavigationStack so you can control in which tab you want to navigate to a specific view. Then, each tab view or any of its children can contribute toolbar items to its navigation stack.
.navigationTitle("Browse")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
favoriteTab.toggle()
} label: {
Image(systemName: favoriteTab ? "heart.fill" : "heart")
}
}
}
If you want to have the same button available in all tabs (like the settings button), you can use a view extension function to define the toolbar item and then use that modifier on any view that should display a settings button in its navigation stack. In the example below, look at the .settingsToolbarItem view extension.
.settingsToolbarItem(showNavigationArrows: true)
In this case, if the settings buttons is available in all tabs, the question becomes in which tab should the Settings view be displayed? For the purposes of the code below, the settings view should be displayed in the Home tab, which requires logic for controlling the selected tab from any view (or function).
In my experience, this is most easily achieved using a shared observable singleton, like the NavigationManager class shown in the example code below, since you can access it or bind to it from anywhere without having to pass it to any or all views that may need it. This class also has properties for the navigation paths of each tab.
//Observable singleton
@Observable
class NavigationManager {
//Properties
var selectedTab: Int = 1
var homeNavigationPath: [NavigationRoute] = []
var browseNavigationPath: NavigationPath = NavigationPath()
var profileNavigationPath: NavigationPath = NavigationPath()
//Singleton
static let nav = NavigationManager()
private init() {}
}
How you want to go about the navigation paths is up to you, but as an example, I showed one way using a navigation route enum configured for the Home tab destinations:
//Navigation route enum
enum NavigationRoute {
case settings
var route: some View {
switch self {
case .settings: SettingsView()
}
}
}
... and the associated config in HomeTabView:
.navigationDestination(for: NavigationRoute.self) { destination in
destination.route
}
With this setup, navigating to Settings in the Home tab becomes as simple as:
let nav = NavigationManager.nav
Button {
//Switch to home tab
nav.selectedTab = 1
//Navigate to settings using home tab's navigation stack
nav.homeNavigationPath.append(.settings)
} label: {
Image(systemName: "gear")
}
You can still use a NavigationLink like in the Profile tab, for example:
.navigationTitle("Profile")
.toolbar {
ToolbarItem(placement: .primaryAction) {
NavigationLink {
VStack {
Text("Some content for adding a profile...")
}
.navigationTitle("Add profile")
} label: {
Label("Add profile", systemImage: "person.crop.circle.badge.plus")
}
}
}
Full code:
import SwiftUI
import Observation
//Root view
struct ContentView: View {
//State values
@State private var nav: NavigationManager = NavigationManager.nav // <- initialize navigation manager singleton
//Body
var body: some View {
TabView(selection: $nav.selectedTab) {
Tab("Home", systemImage: "house", value: 1){
HomeTabView()
}
Tab("Browse", systemImage: "folder", value: 2){
BrowseTabView()
}
Tab("Profile", systemImage: "person.crop.circle", value: 3){
ProfileTabView()
}
}
.tabViewStyle(.sidebarAdaptable)
.tabViewSidebarHeader {
Text("Files")
.font(.largeTitle)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
//Home view
struct HomeTabView: View {
//Binding to observable navigation manager singleton
@Bindable var navigationManager = NavigationManager.nav
//Body
var body: some View {
NavigationStack(path: $navigationManager.homeNavigationPath) {
VStack {
ContentUnavailableView {
Label("No content", systemImage: "questionmark.circle.fill")
} description: {
Text("Nothing to show at this time.")
}
}
.navigationTitle("Home")
.settingsToolbarItem()
.navigationDestination(for: NavigationRoute.self) { destination in
destination.route
}
}
}
}
//Browse view
struct BrowseTabView: View {
//Binding to observable navigation manager singleton
@Bindable var navigationManager = NavigationManager.nav
//State values
@State private var favoriteTab = false
//Body
var body: some View {
NavigationStack(path: $navigationManager.browseNavigationPath) {
VStack {
ContentUnavailableView {
Label("No files", systemImage: "questionmark.folder.fill")
} description: {
Text("No files available.")
}
}
.navigationTitle("Browse")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
favoriteTab.toggle()
} label: {
Image(systemName: favoriteTab ? "heart.fill" : "heart")
}
}
}
.settingsToolbarItem(showNavigationArrows: true)
}
}
}
//Profile view
struct ProfileTabView: View {
//Binding to observable navigation manager singleton
@Bindable var navigationManager = NavigationManager.nav
//Body
var body: some View {
NavigationStack(path: $navigationManager.profileNavigationPath) {
VStack {
ContentUnavailableView {
Label("No profile", systemImage: "person.crop.circle.badge.questionmark.fill")
} description: {
Text("No user profile.")
}
}
.navigationTitle("Profile")
.toolbar {
ToolbarItem(placement: .primaryAction) {
NavigationLink {
VStack {
Text("Some content for adding a profile...")
}
.navigationTitle("Add profile")
} label: {
Label("Add profile", systemImage: "person.crop.circle.badge.plus")
}
}
}
}
}
}
//Settings view
struct SettingsView: View {
var body: some View {
VStack {
ContentUnavailableView {
Label("No settings", systemImage: "gear.badge.questionmark")
} description: {
Text("No settings configured.")
}
}
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .secondaryAction) {
Button {
//action here...
} label: {
Label("Reset settings", systemImage: "gearshape.arrow.trianglehead.2.clockwise.rotate.90")
}
}
}
}
}
//Navigation route enum
enum NavigationRoute {
case settings
var route: some View {
switch self {
case .settings: SettingsView()
}
}
}
//View extension
extension View {
func settingsToolbarItem(showNavigationArrows: Bool = false) -> some View {
self
.toolbar {
ToolbarItem(placement: .primaryAction) {
let nav = NavigationManager.nav
Button {
//Switch to home tab
nav.selectedTab = 1
//Navigate to settings using home tab's navigation stack
nav.homeNavigationPath.append(.settings)
} label: {
Image(systemName: "gear")
}
}
if showNavigationArrows {
ToolbarItemGroup(placement: .topBarLeading) {
Button {
//action here...
} label: {
Image(systemName: "chevron.left")
}
Button {
//action here...
} label: {
Image(systemName: "chevron.right")
}
.disabled(true)
}
}
}
}
}
//Observable singleton
@Observable
class NavigationManager {
//Properties
var selectedTab: Int = 1
var homeNavigationPath: [NavigationRoute] = []
var browseNavigationPath: NavigationPath = NavigationPath()
var profileNavigationPath: NavigationPath = NavigationPath()
//Singleton
static let nav = NavigationManager()
private init() {}
}
#Preview {
ContentView()
}


TabViewshould not be inside aNavigationStackbecause they interfere with each other's way of navigation. Restructure your code.