Whether or not you’re making a social media app or a productiveness instrument, the tab bar interface can improve the consumer expertise by making it extra intuitive and user-friendly. With SwiftUI’s TabView
, making a seamless and customizable tab interface has by no means been simpler.
By default, iOS shows the tab bar in its normal kind, permitting customers to shortly swap between totally different app capabilities with ease. Nonetheless, as a developer, you most likely wish to customise the tab bar to suit the precise wants of your app.
On this tutorial, you’ll learn to create a scrollable and animated tab bar which helps infinite tab objects utilizing SwiftUI. Check out the top end result under to get a glimpse of what you’ll be capable of obtain by the top of the tutorial.

Introducing Tab View and Tab Bar
If you happen to haven’t used TabView
earlier than, let’s have a fast stroll by. To create a tab view, you simply want to make use of TabView
and embed the kid views inside. For every of the kid views, you apply the tabItem
modifier to specify the merchandise description. Right here is an instance:
var physique: some View {
TabView {
ForEach(colours.indices, id: .self) { index in
colours[index]
.body(maxWidth: .infinity, maxHeight: .infinity)
.tag(index)
.tabItem {
Picture(systemName: “(index + 1).circle”)
Textual content(tabbarItems[index])
}
}
}
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
struct ContentView: View {     let colours: [Color] = [ .yellow, .blue, .green, .indigo, .brown ]     let tabbarItems = [ “Random”, “Travel”, “Wallpaper”, “Food”, “Interior Design” ]      var physique: some View {         TabView {             ForEach(colours.indices, id: .self) { index in                 colours[index]                     .body(maxWidth: .infinity, maxHeight: .infinity)                     .tag(index)                     .tabItem {                         Picture(systemName: “(index + 1).circle”)                         Textual content(tabbarItems[index])                     }             }         }     } } |
The code above creates a easy tab view with 5 tab objects. You utilize the Picture
view to show the tab icon. If you happen to’ve written the code in Xcode, it’s best to see a tab bar within the preview.

The TabView
has one other init
methodology for this goal. The strategy requires a state variable which incorporates the tag worth of the tab.
TabView(choice: $selectedIndex) |
For instance, declare the next state variable in ContentView
:
@State non-public var selectedIndex = 0 |
Now should you change the worth of selectedIndex
, the tab view will robotically swap to the corresponding tab. Chances are you’ll modify the code like this to check it out:
TabView(choice: $selectedIndex) { Â Â . Â Â . Â Â . } .onAppear { Â Â Â Â selectedIndex = 2 } |
When the tab view seems, the third tab is robotically chosen.
Constructing a Customized Scrollable Tab Bar

As you possibly can see within the closing end result above, the tab bar is scrollable, which is especially helpful when you should accomodate greater than 5 objects. To construct this practice tab bar, we are going to use each ScrollView
and ScrollViewReader
to create our personal view.
Let’s identify our tab bar view TabBarView
and create it like this:
@State var selectedIndex = 0
var physique: some View {
ScrollViewReader { scrollView in
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(tabbarItems.indices, id: .self) { index in
Textual content(tabbarItems[index])
.font(.subheadline)
.padding(.horizontal)
.padding(.vertical, 4)
.foregroundColor(selectedIndex == index ? .white : .black)
.background(Capsule().foregroundColor(selectedIndex == index ? .purple : .clear))
.onTapGesture {
withAnimation(.easeInOut) {
selectedIndex = index
}
}
}
}
}
.padding()
.background(Shade(.systemGray6))
.cornerRadius(25)
}
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
struct TabBarView: View {     var tabbarItems: [String]      @State var selectedIndex = 0      var physique: some View {         ScrollViewReader { scrollView in             ScrollView(.horizontal, showsIndicators: false) {                 HStack {                     ForEach(tabbarItems.indices, id: .self) { index in                          Textual content(tabbarItems[index])                             .font(.subheadline)                             .padding(.horizontal)                             .padding(.vertical, 4)                             .foregroundColor(selectedIndex == index ? .white : .black)                             .background(Capsule().foregroundColor(selectedIndex == index ? .purple : .clear))                             .onTapGesture {                                 withAnimation(.easeInOut) {                                     selectedIndex = index                                 }                             }                     }                 }             }             .padding()             .background(Shade(.systemGray6))             .cornerRadius(25)          }      } } |
This tradition tab view accepts an array of tab bar objects. For demo functions, we’re utilizing a String
array. Nonetheless, in real-world functions, it’s possible you’ll wish to create your personal customized sort for the tab merchandise.
To allow scrolling inside the tab bar, we’ve embedded all the tab objects in a scroll view. Moreover, we’ve wrapped the scroll view with a scroll view reader to make sure that the chosen tab merchandise is all the time seen.
When a selected tab merchandise is chosen, we replace the selectedIndex
variable to replicate the chosen index. This permits us to focus on the energetic tab merchandise and supply suggestions to the consumer.

To preview this practice tab bar, you possibly can add the TabBarView
to the preview like this:
TabBarView(tabbarItems: [ “Random”, “Travel”, “Wallpaper”, “Food”, “Interior Design” ]).previewDisplayName(“TabBarView”)
}
}
struct ContentView_Previews: PreviewProvider { Â Â Â Â static var previews: some View { Â Â Â Â Â Â Â Â ContentView() Â Â Â Â Â Â Â Â Â TabBarView(tabbarItems: [ “Random”, “Travel”, “Wallpaper”, “Food”, “Interior Design” ]).previewDisplayName(“TabBarView”) Â Â Â Â } } |
Proper now, the customized tab bar works fairly good. Nonetheless, it’s possible you’ll discover that you should manually scroll the tab bar with the intention to reveal the final merchandise. To repair this subject, you possibly can connect the next code to the ScrollView
:
.onChange(of: selectedIndex) { index in     withAnimation {         scrollView.scrollTo(index, anchor: .middle)     } } |
When the chosen index is up to date, we name the scrollTo
methodology to maneuver the scroll view.
Rework the Animation with matchedGeometryEffect
You’ve constructed a dynamic and scrollable tab bar, however wouldn’t or not it’s nice if we are able to make the animation even higher? Presently, the tab bar makes use of a fade animation when switching between tab objects. By incorporating matchedGeometryEffect
into the tab bar, you possibly can create a a lot smoother and visually interesting animation. Let’s see find out how to implement it.
First, let’s create a brand new struct known as TabbarItem
for the tab bar merchandise like this:
var physique: some View {
if isActive {
Textual content(identify)
.font(.subheadline)
.padding(.horizontal)
.padding(.vertical, 4)
.foregroundColor(.white)
.background(Capsule().foregroundColor(.purple))
.matchedGeometryEffect(id: “highlightmenuitem”, in: namespace)
} else {
Textual content(identify)
.font(.subheadline)
.padding(.horizontal)
.padding(.vertical, 4)
.foregroundColor(.black)
}
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
struct TabbarItem: View {     var identify: String     var isActive: Bool = false     let namespace: Namespace.ID      var physique: some View {         if isActive {             Textual content(identify)                 .font(.subheadline)                 .padding(.horizontal)                 .padding(.vertical, 4)                 .foregroundColor(.white)                 .background(Capsule().foregroundColor(.purple))                 .matchedGeometryEffect(id: “highlightmenuitem”, in: namespace)         } else {             Textual content(identify)                 .font(.subheadline)                 .padding(.horizontal)                 .padding(.vertical, 4)                 .foregroundColor(.black)         }      } } |
With matchedGeometryEffect
, all you want is describe the looks of two views. The modifier will then compute the distinction between these two views and robotically animates the scale/place modifications. So within the code above, we spotlight the tab merchandise in purple when it’s energetic. In any other case, we show a traditional textual content fashion.
Within the TabBarView
, declare a brand new namespace variable:
@Namespace non-public var menuItemTransition |
After which, rewrite the code of the ForEach
loop like this:
TabbarItem(identify: tabbarItems[index], isActive: selectedIndex == index, namespace: menuItemTransition)
.onTapGesture {
withAnimation(.easeInOut) {
selectedIndex = index
}
}
}
ForEach(tabbarItems.indices, id: .self) { index in      TabbarItem(identify: tabbarItems[index], isActive: selectedIndex == index, namespace: menuItemTransition)         .onTapGesture {             withAnimation(.easeInOut) {                 selectedIndex = index             }         } } |
When you made the change, it’s best to discover a a lot better animation when switching between tab objects.

Utilizing the Customized Tab Bar
We have now to make a minor change within the TabBarView
earlier than we are able to apply it to our ContentView
. In TabBarView
, modify the state variable to a binding variable like this:
@Binding var selectedIndex: Int |
Now you’re prepared to make use of this practice tab bar in different views. In ContentView
, replace the physique
half like this:
TabBarView(tabbarItems: tabbarItems, selectedIndex: $selectedIndex)
.padding(.horizontal)
}
ZStack(alignment: .backside) {     TabView(choice: $selectedIndex) {         ForEach(colours.indices, id: .self) { index in             colours[index]                 .body(maxWidth: .infinity, maxHeight: .infinity)                 .tag(index)                 .ignoresSafeArea()         }     }     .ignoresSafeArea()      TabBarView(tabbarItems: tabbarItems, selectedIndex: $selectedIndex)         .padding(.horizontal) } |
Incorporating the customized tab bar into your app is a simple course of. By wrapping the TabView
in a ZStack
and overlaying the TabBarView
on prime of it, you possibly can simply combine the tab bar into the tab UI.
To make the venture run easily, you additionally must replace the preview struct like this:
TabBarView(tabbarItems: [ “Random”, “Travel”, “Wallpaper”, “Food”, “Interior Design” ], selectedIndex: .fixed(0)).previewDisplayName(“TabBarView”)
}
}
struct ContentView_Previews: PreviewProvider { Â Â Â Â static var previews: some View { Â Â Â Â Â Â Â Â ContentView() Â Â Â Â Â Â Â Â Â TabBarView(tabbarItems: [ “Random”, “Travel”, “Wallpaper”, “Food”, “Interior Design” ], selectedIndex: .fixed(0)).previewDisplayName(“TabBarView”) Â Â Â Â } } |
Now you’re prepared to check the tab UI.

Wrap Up
The tab bar interface is a vital part of many in style cellular apps, offering customers with fast and quick access to numerous app capabilities. Whereas the usual tab bar usually suffices for many eventualities, there could also be events if you wish to create a customized tab bar to boost the consumer expertise.
On this tutorial, you’ve discovered find out how to create a dynamic and scrollable tab bar that may accommodate an infinite variety of tab objects. By incorporating matchedGeometryEffect
, you may also take your tab bar’s animation to the following stage. With the methods lined, you’ll be capable of design a seamless and intuitive customized tab bar that matches your app’s particular wants.
If you wish to dive deeper into SwiftUI, you possibly can try our Mastering SwiftUI ebook.