Creating a Custom SwiftUI Tab Bar

SwiftUI provides a simple and declarative way to build user interfaces for Apple platforms. While SwiftUI’s built-in TabView offers a basic tab bar, you may need a custom solution for greater flexibility or unique design requirements. Creating a custom SwiftUI tab bar allows you to tailor the appearance and behavior to perfectly match your app’s aesthetic.

What is a Custom Tab Bar?

A custom tab bar is a navigation component that allows users to switch between different views or sections in an application, implemented from scratch or by significantly modifying the default tab bar behavior. Custom tab bars are designed to provide a unique look and feel that aligns with the app’s design language, and can offer advanced features beyond what a standard TabView provides.

Why Create a Custom Tab Bar?

  • Design Flexibility: Full control over appearance and animation.
  • Unique Aesthetics: Implement designs not possible with the default tab bar.
  • Advanced Functionality: Add custom behaviors such as interactive animations, badge indicators, or dynamic tab content.

How to Create a Custom SwiftUI Tab Bar

Creating a custom tab bar involves several steps, from setting up the basic layout to handling tab selection and view transitions. Below are the steps with detailed explanations and code examples.

Step 1: Set Up the Basic Layout

First, you need to create the basic structure for your tab bar. This includes creating a state variable to track the selected tab and arranging your content views using a ZStack and an HStack.


import SwiftUI

struct CustomTabBarView: View {
    @State private var selectedTab: Int = 0

    var body: some View {
        ZStack(alignment: .bottom) {
            // Content Views
            TabView(selection: $selectedTab) {
                HomeView()
                    .tag(0)
                    .tabItem {
                        Image(systemName: "house")
                        Text("Home")
                    }

                SearchView()
                    .tag(1)
                    .tabItem {
                        Image(systemName: "magnifyingglass")
                        Text("Search")
                    }

                SettingsView()
                    .tag(2)
                    .tabItem {
                        Image(systemName: "gear")
                        Text("Settings")
                    }
            }
            
            // Custom Tab Bar
            CustomTabBar(selectedTab: $selectedTab)
        }
    }
}

struct HomeView: View {
    var body: some View {
        Text("Home View")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.red.opacity(0.3))
    }
}

struct SearchView: View {
    var body: some View {
        Text("Search View")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.green.opacity(0.3))
    }
}

struct SettingsView: View {
    var body: some View {
        Text("Settings View")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.blue.opacity(0.3))
    }
}

In this setup:

  • A ZStack is used to layer the content views and the custom tab bar.
  • TabView is a standard SwiftUI component to present different tabs. We add a tag to each to identify them. The .tabItem is required to exist, but these are unused for a completely custom tab bar.
  • The selectedTab state variable is bound to the selection parameter of the TabView.
  • CustomTabBar is the custom tab bar we will implement in the next steps.

Step 2: Implement the Custom Tab Bar

Next, you’ll create the CustomTabBar view, which contains the buttons or icons for switching between tabs. Use an HStack to lay out the tab bar items horizontally.


struct CustomTabBar: View {
    @Binding var selectedTab: Int
    private let tabItems: [(image: String, text: String)] = [
        ("house.fill", "Home"),
        ("magnifyingglass", "Search"),
        ("gear", "Settings")
    ]

    var body: some View {
        HStack {
            ForEach(0..

Here’s a breakdown:

  • The selectedTab state variable is passed as a binding.
  • An HStack arranges the tab bar items horizontally.
  • Each tab bar item is a Button. When tapped, it updates the selectedTab state.
  • The foreground color of each tab item changes based on whether it is selected, providing visual feedback.

Step 3: Style and Animate the Tab Bar

To enhance the user experience, add styling and animations to the tab bar. You can use various SwiftUI modifiers to customize the appearance and add transitions.


struct CustomTabBar: View {
    @Binding var selectedTab: Int
    @Namespace private var animationNamespace
    private let tabItems: [(image: String, text: String)] = [
        ("house.fill", "Home"),
        ("magnifyingglass", "Search"),
        ("gear", "Settings")
    ]

    var body: some View {
        HStack {
            ForEach(0..

Key improvements include:

  • @Namespace for Matched Geometry Effect: Declares a namespace for custom animations within this view.
  • Animation on Tab Selection: Uses withAnimation for a smooth transition when a tab is selected. .spring() creates a bouncy and dynamic animation.
  • Animated Indicator: The conditional background draws a blue line beneath the active tab to act as a selection indicator. The matchedGeometryEffect animates this line smoothly to the newly selected tab, using a shared namespace id

Step 4: Add Custom Behaviors (Optional)

You can further customize the tab bar by adding custom behaviors, such as badge indicators, interactive animations, or dynamic content updates. This involves modifying the CustomTabBar view and potentially adding more state variables to manage the custom logic.

For example, let’s add a badge indicator to one of the tabs:


struct CustomTabBar: View {
    @Binding var selectedTab: Int
    @Namespace private var animationNamespace
    @State private var showBadge: Bool = true
    
    private let tabItems: [(image: String, text: String)] = [
        ("house.fill", "Home"),
        ("magnifyingglass", "Search"),
        ("gear", "Settings")
    ]

    var body: some View {
        HStack {
            ForEach(0..

The key update:

  • Badge is only applied to the “Search” (index 1) tab icon using a conditional.
  • If showBadge is true and it’s the “Search” tab, a small red circle appears as a badge.

Complete Code

Here is the complete code for custom tab bar:


import SwiftUI

struct CustomTabBarView: View {
    @State private var selectedTab: Int = 0

    var body: some View {
        ZStack(alignment: .bottom) {
            // Content Views
            TabView(selection: $selectedTab) {
                HomeView()
                    .tag(0)
                    .tabItem {
                        Image(systemName: "house")
                        Text("Home")
                    }

                SearchView()
                    .tag(1)
                    .tabItem {
                        Image(systemName: "magnifyingglass")
                        Text("Search")
                    }

                SettingsView()
                    .tag(2)
                    .tabItem {
                        Image(systemName: "gear")
                        Text("Settings")
                    }
            }
            
            // Custom Tab Bar
            CustomTabBar(selectedTab: $selectedTab)
        }
    }
}

struct HomeView: View {
    var body: some View {
        Text("Home View")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.red.opacity(0.3))
    }
}

struct SearchView: View {
    var body: some View {
        Text("Search View")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.green.opacity(0.3))
    }
}

struct SettingsView: View {
    var body: some View {
        Text("Settings View")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.blue.opacity(0.3))
    }
}

struct CustomTabBar: View {
    @Binding var selectedTab: Int
    @Namespace private var animationNamespace
    @State private var showBadge: Bool = true
    
    private let tabItems: [(image: String, text: String)] = [
        ("house.fill", "Home"),
        ("magnifyingglass", "Search"),
        ("gear", "Settings")
    ]

    var body: some View {
        HStack {
            ForEach(0..

Conclusion

Creating a custom tab bar in SwiftUI gives you the freedom to design and implement unique navigation experiences that go beyond the capabilities of the default TabView. By using SwiftUI's powerful layout and animation features, you can create a tab bar that is not only visually appealing but also enhances the usability of your app.