The hidden secrets of TabView in iOS with SwiftUI

TabBar is a vital component of iOS and has been from iOS 2.0. This element appears at the bottom of the iOS and iPadOS devices and allows our app users to switch between different views or functions quickly.

It is a major element of Apple's apps like Music, Podcasts, and App Store.

SwiftUI DisclosureGroup

In SwiftUI, we have a new element name TabView instead of UITabBar in UIKit. In this post, we will look into how to use it, especially how the TabView can be used to show page indicators.

Ways to initialize TabView in SwiftUI

Right now we have two options to create a tab view with SwiftUI. We can either take control of the selected tab or avoid it whatsoever. Let's look into both of these approaches.

Create the TabView with SwiftUI

To create a TabView element, we need to pass the Content that is a list of SwiftUI views. To mark this view as a tab bar item, we need to use the tabItem view modifier passing inside a Label that describes a title and image.

Let's now put all this together in the code.

TabView {
  Text("The First Tab")
    .tabItem {
      Label("First", systemImage: "1.square.fill")
    }
  
  Text("The Second Tab")
    .tabItem {
      Label("Second", systemImage: "2.square.fill")
    }
  
  Text("The Third Tab")
    .tabItem {
      Label("Third", systemImage: "3.square.fill")
    }
}

Now we can unpack the code above. Each tab is just a Text view, and the tab item is a Label with a title and system image that is an SF Symbol.

SwiftUI DisclosureGroup

Control the selected tab programmatically

Suppose we want to control the selected tab programmatically. In that case, we need to use the second approach to initialize the method where besides the content, we need to pass a state variable that keeps the currently selected tab. Another critical step is to mark each tab with a tag modifier with a value that conforms to the Hashable protocol. This is how the SwiftUI layout engine differs between the tabs and can understand the uniqueness.

TabView(selection: $selectedTab) {
  VStack {
    Button("Move to Second Tab") {
      selectedTab = 2
    }
  }
  .tabItem {
    Label("First", systemImage: "1.square.fill")
  }
  .tag(1)
  
  VStack {
    Button("Move to Third Tab") {
      selectedTab = 3
    }
  }
  .tabItem {
    Label("Second", systemImage: "2.square.fill")
  }
  .tag(2)
  
  VStack {
    Button("Move to First Tab") {
      selectedTab = 1
    }
  }
  .tabItem {
    Label("Third", systemImage: "3.square.fill")
  }
  .tag(3)
}

Let's go over the code above step by step.

Firstly we are using the VStack views with a Button inside to select the next tab or the last the first accordingly.

Then we are using the tag modifier for each of the tab items. We are using the Int value type to keep track of the currently selected tab. We are not limited to the Int value type, which conforms to the Hashable protocol, but we can use our type if we want to. For the sake of simplicity, we are using the Int this time.

Select current tab programmatically

Customize TabView

Sadly there aren't many options for customizing the TabView in SwiftUI. We can change the image and title for the tabs, but that is quite understandable.

We can change the default accent color. Let's change it to purple.

TabView {
 // ...
}
.accentColor(.purple)
SwiftUI TabView with accentColor

TabView styles in SwiftUI

In SwiftUI, we are not limited to the regular tab bar style. Right now, we have three ways how to change the TabView styles:

  • DefaultTabViewStyle - regular style that we all know;
  • PageTabViewStyle - scrolling pages with the page indicator;
  • CarouselTabViewStyle - specific style for watchOS that we're not going to look into in this post.

Create scrolling pages

To create scrolling pages with the TabView in SwiftUI, we need to call the view modifier tabViewStyle and pass an instance of PageTabViewStyle style.

TabView {
  Color.gray
  Color.green
  Color.blue
}
.tabViewStyle(PageTabViewStyle())

If we want to hide the page indicator, we specify the indexDisplayMode parameter for the PageTabViewStyle instance and set it to .never.

TabView {
  // ...
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
SwiftUI TabView with scrolling pages

TL;DR

TabBar on the bottom of the screen is one of the most important building blocks for modern iOS applications. Apple themselves is using it frequently in their apps.

With SwiftUI, this element now has the new name TabView. It allows us to add the tab view and control the currently selected tab programmatically.

The hidden feature of the TabView is that we can use it to show the multiple tabs with page indicators, and those can be controlled by scrolling between them. In UIKit, it was UIPageViewController.

Links