SwiftUI

Custom TabBar SwiftUI

let’s start with the Tabbar. For each tab bar item, we need a title, image, and selected image. So we need to create a struct name TabItemData. struct TabItemData { let image: String let selectedImage: String ...

J
Joynal Abedin
5
let’s start with the Tabbar. For each tab bar item, we need a title, image, and selected image. So we need to create a struct name TabItemData.
struct TabItemData {
    let image: String
    let selectedImage: String
    let title: String
}
Then let prepare the TabItemView, which is one of three items in the Tabbar. We have an image and text center vertical, so let wrap it into a VStack. Also, it has two states one is selected and one is un-selected. The input of this TabItemView should be TabItemData and isSelected.
struct TabItemView: View {
    let data: TabItemData
    let isSelected: Bool
    
    var body: some View {
        VStack {
            Image(isSelected ? data.selectedImage : data.image)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 32, height: 32)
                .animation(.default)
            
            Spacer().frame(height: 4)
            
            Text(data.title)
                .foregroundColor(isSelected ? .black : .gray)
                .font(.system(size: 14))
        }
    }
}
Next, We will build the custom Tabbar, we will name it TabBottomView . In this design, it has three items, horizontally center, so we will wrap them into an HStack, round all corners, and add a white background with a smooth shadow effect.
struct TabBottomView: View {
    
    let tabbarItems: [TabItemData]
    var height: CGFloat = 70
    var width: CGFloat = UIScreen.main.bounds.width - 32
    @Binding var selectedIndex: Int
    
    var body: some View {
        HStack {
            Spacer()
            
            ForEach(tabbarItems.indices) { index in
                let item = tabbarItems[index]
                Button {
                    self.selectedIndex = index
                } label: {
                    let isSelected = selectedIndex == index
                    TabItemView(data: item, isSelected: isSelected)
                }
                Spacer()
            }
        }
        .frame(width: width, height: height)
        .background(Color.white)
        .cornerRadius(13)
        .shadow(radius: 5, x: 0, y: 4)
    }
}
In this view, we will allow change width and height from outside. So the input should be tabbarItemswidthheight , and binding variable selectedIndex . We will wrap the TabItemView into a Button because we need to handle it when users touch in, we will update the selectedIndex .

And the final thing we will build in this tutorial is CustomTabView. We aim to reuse this component in other projects, we will create a view that can adapt with any View. Therefore, we will use the attribute name ViewBuilder. For more information, you guys can read it. Thank Majid for a nice post.

The input of CustomTabView is tab item array, selectedIndex, and content view for each tab. Because we don’t know what view we will put into in the future, that why we need to make this view Generic with 

Content for each tab we will get from the closure@ViewBuilder let content: (Int) -> Content , with Int is the index of that tab. Let wrap it into a TabView by a ForEach, and don’t forget to set tag for it content(index).tag(index)

struct CustomTabView: View {
    
    let tabs: [TabItemData]
    @Binding var selectedIndex: Int
    @ViewBuilder let content: (Int) -> Content
    
    var body: some View {
        ZStack {
            TabView(selection: $selectedIndex) {
                ForEach(tabs.indices) { index in
                    content(index)
                        .tag(index)
                }
            }
            
            VStack {
                Spacer()
                TabBottomView(tabbarItems: tabs, selectedIndex: $selectedIndex)
            }
            .padding(.bottom, 8)
        }
    }
}

Back to the design, as we can see here, the content of each tab shows in full screen, and the tab bar seems to float. So we will use ZStack in this case. If you don’t want it, just use VStack.

Voila, we finished building the CustomTabView. Let try to use it.

We need to prepare 4 views HomeView, MyFileView, ProfileView, MainTabView to use our CustomTabView and an enum TabType

enum TabType: Int, CaseIterable {
    case home = 0
    case myFile
    case profile
    
    var tabItem: TabItemData {
        switch self {
        case .home:
            return TabItemData(image: \"ic_home\", selectedImage: \"ic_home_selected\", title: \"Home\")
        case .myFile:
            return TabItemData(image: \"ic_myfile\", selectedImage: \"ic_myfile_selected\", title: \"My File\")
        case .profile:
            return TabItemData(image: \"ic_profile\", selectedImage: \"ic_profile_selected\", title: \"Me\")
        }
    }
}

Implement the MainTabView. We will use ViewBuilder again, in another way.

struct MainTabView: View {
    
    @State var selectedIndex: Int = 0
    
    var body: some View {
        CustomTabView(tabs: TabType.allCases.map({ $0.tabItem }), selection: $selectedIndex) { index in
            let type = TabType(rawValue: index) ?? .home
            getTabView(type: type)
        }
    }
    
    @ViewBuilder
    func getTabView(type: TabType) -> some View {
        switch type {
        case .home:
            HomeView()
        case .myFile:
            MyFileView()
        case .profile:
            ProfileView()
        }
    }
}
Output:   \"\"   References: Truong Van Klen GithubRepo: https://github.com/Joynal279/TabBarDemoApp
J

Written by Joynal Abedin

Passionate about technology, code, and sharing knowledge.

0 Comments

Leave a Comment