SwiftUI · · 6 min read

Introducing SwiftUI TabView and Working with Tab Bar Customization

Introducing SwiftUI TabView and Working with Tab Bar Customization

The tab bar interface appears in some of the most popular mobile apps such as Facebook, Instagram, and Twitter. A tab bar appears at the bottom of an app screen and let users quickly switch between different functions of an app. In UIKit, you use the UITabBarController to create the tab bar interface. For the SwiftUI framework, it provides a UI component called TabView for developers to display tabs in the apps.

In this tutorial, we will show you how to create a tab bar interface using TabView, handle the tab selection, and customize the appearance of the tab bar. All the source code below are tested on Xcode 12. If you haven’t upgraded to the latest version of Xcode, it’s better to upgrade the development tool to version 12.

Using TabView to Create the Tab Bar Interface

Assuming you’ve created a SwiftUI project using Xcode 12, let’s start with a simple text view like this:

struct ContentView: View {
    var body: some View {
        Text("Home Tab")
            .font(.system(size: 30, weight: .bold, design: .rounded))
    }
}

To embed this text view in a tab bar, all you need to do is wrap it with the TabView component and set the tab item description by attaching the .tabItem modifier like this:

struct ContentView: View {
    var body: some View {
        TabView {
            Text("Home Tab")
                .font(.system(size: 30, weight: .bold, design: .rounded))
                .tabItem {
                    Image(systemName: "house.fill")
                    Text("Home")
                }
        }
    }
}

This will create a tab bar with a single tab item. In the sample code, the tab item has both image and text, but you are free to remove one of the those.

swiftui-tab-view

To display more tabs, you just need to add the child views inside the TabView like this:

TabView {
    Text("Home Tab")
        .font(.system(size: 30, weight: .bold, design: .rounded))
        .tabItem {
            Image(systemName: "house.fill")
            Text("Home")
        }

    Text("Bookmark Tab")
        .font(.system(size: 30, weight: .bold, design: .rounded))
        .tabItem {
            Image(systemName: "bookmark.circle.fill")
            Text("Bookmark")
        }

    Text("Video Tab")
        .font(.system(size: 30, weight: .bold, design: .rounded))
        .tabItem {
            Image(systemName: "video.circle.fill")
            Text("Video")
        }

    Text("Profile Tab")
        .font(.system(size: 30, weight: .bold, design: .rounded))
        .tabItem {
            Image(systemName: "person.crop.circle")
            Text("Profile")
        }
}

This gives you a tab bar interface with 4 tab items.

swiftui-tabview-four-tabs

Customizing the Tab Bar Color

By default, the color of the tab bar item is set to blue. You can change its color by attaching the .accentColor modifier to TabView like this:

TabView {

}
.accentColor(.red)

Yet the SwiftUI framework doesn’t have a built-in modifier for changing the tab bar’s color. If you want to change that, you may use the appearance API of UIKit like below:

.onAppear() {
    UITabBar.appearance().barTintColor = .white
}

If you attach the call to TabView, the color of the tab bar should be changed to white.

swiftui-tab-bar-customization

Switching Between Tabs Programmatically

Users can tap the tab bar items to switch between tabs, which is automatically handled the TabView. In some use cases, you may want to switch to a specific tab programmatically. The TabView has another init method for this purpose. The method takes a state variable which associates with the tag value of the tabs.

TabView(selection: $selection)

As an example, declare the following state variable in ContentView:

@State private var selection = 0

Here we initialize the selection variable with a value of 0, which is the tag value of the first tab item. We haven’t defined the tag value for the tab items yet. Therefore, update the code like this and attach the tag modifier for each of the tab items:

TabView(selection: $selection) {
    Text("Home Tab")
        .font(.system(size: 30, weight: .bold, design: .rounded))
        .tabItem {
            Image(systemName: "house.fill")
            Text("Home")
        }
        .tag(0)

    Text("Bookmark Tab")
        .font(.system(size: 30, weight: .bold, design: .rounded))
        .tabItem {
            Image(systemName: "bookmark.circle.fill")
            Text("Bookmark")
        }
        .tag(1)

    Text("Video Tab")
        .font(.system(size: 30, weight: .bold, design: .rounded))
        .tabItem {
            Image(systemName: "video.circle.fill")
            Text("Video")
        }
        .tag(2)

    Text("Profile Tab")
        .font(.system(size: 30, weight: .bold, design: .rounded))
        .tabItem {
            Image(systemName: "person.crop.circle")
            Text("Profile")
        }
        .tag(3)
}

We give each tab item a unique index by attaching the tag modifier. The TabView is also updated to take in the binding to the selection value. To switch to your preferred tab programmatically, all you need to do is to update the value of the selection variable.

Say, you can create a Next button that switches to the next tab like this:

ZStack(alignment: .topTrailing) {
    TabView(selection: $selection) {
        .
        .
        .
    }
    .accentColor(.red)
    .onAppear() {
        UITabBar.appearance().barTintColor = .white
    }

    Button(action: {
        selection = (selection + 1) % 4
    }) {
        Text("Next")
            .font(.system(.headline, design: .rounded))
            .padding()
            .foregroundColor(.white)
            .background(Color.red)
            .cornerRadius(10.0)
            .padding()

    }
}

After making the changes and run the app in the preview canvas, you can switch to the tabs by tapping the Next button.

swiftui-change-tab-programmatically

Hiding the Tab Bar in a Navigation View

You can embed a tab view in a navigation view by wrapping the TabView component with NavigationView like this:

NavigationView {
    TabView(selection: $selection) {
        .
        .
        .
    }

    .navigationTitle("TabView Demo")
}

In UIKit, there is another option called hidesBottomBarWhenPushed, which allows you to hide the tab bar when the UI is pushed to the detail view in a navigation interface. SwiftUI also has this feature built-in. You can modify the code like this to have a test:

NavigationView {
    TabView(selection: $selection) {
        List(1...10, id: \.self) { index in
            NavigationLink(
                destination: Text("Item #\(index) Details"),
                label: {
                    Text("Item #\(index)")
                        .font(.system(size: 20, weight: .bold, design: .rounded))
                })

        }
        .tabItem {
            Image(systemName: "house.fill")
            Text("Home")
        }
        .tag(0)

        Text("Bookmark Tab")
            .font(.system(size: 30, weight: .bold, design: .rounded))
            .tabItem {
                Image(systemName: "bookmark.circle.fill")
                Text("Bookmark")
            }
            .tag(1)

        Text("Video Tab")
            .font(.system(size: 30, weight: .bold, design: .rounded))
            .tabItem {
                Image(systemName: "video.circle.fill")
                Text("Video")
            }
            .tag(2)

        Text("Profile Tab")
            .font(.system(size: 30, weight: .bold, design: .rounded))
            .tabItem {
                Image(systemName: "person.crop.circle")
                Text("Profile")
            }
            .tag(3)
    }
    .accentColor(.red)
    .onAppear() {
        UITabBar.appearance().barTintColor = .white
    }

    .navigationTitle("TabView Demo")
}

We just altered the code of the Home tab to display a list of item. And, we wrap each list item with a NavigationLink, so that it will navigate to the detail view when the item is tapped. If you run the app using a simulator or in the preview canvas, you should see that the tab bar is hidden when it’s navigated to the detail view.

swiftui-navigation-view-hide-tab-bar

For some scenarios, you probably don’t want the tab bar to be hidden. In this case, you can create the navigation interface the other way round. Instead of wrapping the tab view in a navigation view, you embed the navigation view in a tab view like this:

TabView(selection: $selection) {
    NavigationView {
        List(1...10, id: \.self) { index in
            NavigationLink(
                destination: Text("Item #\(index) Details"),
                label: {
                    Text("Item #\(index)")
                        .font(.system(size: 20, weight: .bold, design: .rounded))
                })

        }

        .navigationTitle("TabView Demo")
    }
    .tabItem {
        Image(systemName: "house.fill")
        Text("Home")
    }
    .tag(0)

    .
    .
    .
}

Now when you navigate to the detail view of the item, the tab bar is still there.

swiftui-navigation-view-show-tab-bar

Summary

In this tutorial, we have walked you through the basics of TabView, which is the UI component in SwiftUI for building a tab view interface. The framework doesn’t provide you with much options for customizing the tab bar. However, you may still rely on the APIs of UIKit to customizing the appearance. This tutorial only shows you how to work with the built-in tab bar. You can actually create your own tab bar if you need full customizations. We will discuss more about that in the future tutorials.

If you want to learn more about SwiftUI, please further check out our Mastering SwiftUI book. It has been fully updated for Xcode 12 and iOS 14.

Read next