Programmatic navigation with Tab View in SwiftUI

Programmatic navigation with Tab View in SwiftUI

Learn how to handle trigger navigation in Tab Views through code in a SwiftUI app.

When we want direct control over navigation in a SwiftUI app, like presenting a specific view, we use programmatic navigation. This pattern lets us trigger navigation based on a value, instead of waiting for user interaction.

An example is the Fitness app on iOS, after finishing the Trainer Tips video content in the summary tab, the user will automatically be redirected to the Fitness+ tab.

To implement this type of navigation we first need to define a type that represents each tab.

enum NavigationTabs {
    case make-it-accessible
    case make-it-spatial
    case make-it-intelligent
}

In the example above, we have defined the enumeration with three different possible values.

Now, within a view, declare a state property to track which is the currently selected tab, initialized with the value of the default selection.

import SwiftUI

struct ContentView: View {

    @State var selectedTab : NavigationTabs = .make_it_accessible
    
    var body: some View {
        // TODO
    }
}

With all set, just implement the TabView container binding the state property to the selection parameter in its initializer.

import SwiftUI

struct ContentView: View {

    @State var selectedTab : NavigationTabs = .make_it_accessible
    
    var body: some View {
        TabView(selection: $selectedTab) {
            // TODO: Add tabs
        }
    }
}

For assigning a selection value to each tab use the init(_:systemImage:value:content:) initializer. Every time that a tab is presented the selectedTab property will update its value according to the displayed tab.

import SwiftUI

struct ContentView: View {

    @State var selectedTab : NavigationTabs = .make_it_accessible
    
    var body: some View {
        TabView(selection: $selectedTab) {
            Tab("Accessibility", systemImage: "accessibility", value: .make_it_accessible) {
                Text("Make it accessible")
            }
            
            Tab("Spatial computing", systemImage: "visionpro", value: .make_it_accessible) {
                Text("Make it Spatial")
            }
            
            Tab("Machine learning", systemImage: "lightbulb.min.fill", value: .make_it_accessible) {
                Text("Make it Intelligent")
            }
        }
    }
    
}

To directly navigate to a specific tab, you can implement a button that modifies the selectedTab property. When triggered, the button updates the selectedTab value, dynamically rendering the corresponding tab.

This approach enables developers to design custom tab bars, also by leveraging the new init(value:content:) initializer, which sets the label of the tab as an empty label.

struct ContentView: View {

    @State var selectedTab : NavigationTabs = .make_it_accessible
    
    var body: some View {
        ZStack {
            TabView(selection: $selectedTab)  {
                Tab(value: .make_it_accessible) {
                    ContentUnavailableView("Make it Accessible", systemImage: "accessibility")
                        .toolbarVisibility(.hidden, for: .tabBar)
                }
                
                Tab(value: .make_it_spatial) {
                    ContentUnavailableView("Make it Spatial", systemImage: "visionpro")
                        .toolbarVisibility(.hidden, for: .tabBar)
                }
                
                Tab(value: .make_it_accessible) {
                    ContentUnavailableView("Make it Intelligent", systemImage: "lightbulb.min.fill")
                        .toolbarVisibility(.hidden, for: .tabBar)
                }
            }
            
            CustomTabBarView(selectedTab: $selectedTab)
        }
    }
    
}

In the example above we have also applied to each view the toolbarVisibility(_:for:) modifier so that the tab bar will be completely disabled.

Custom tab bar example

In this way developers can maintain the functionality of the Tab component while applying their own custom designs and interactions, preserving the structural benefits of the tab navigation system.