Custom Top Tab Bar in IOS (Swift, SwiftUI)

 


Hi Guys, in this blog you can learn how to make a Custom Top Tab Bar in IOS by using Swift & SwiftUI. For better understanding please read the complete blog. Thanks :)

Video:


Step-1) 

Create an XCode project with SwiftUI. Look at the following screenshot. 


Step-2) 

Now create two dummy view's which we will show on every tab selection. Like here I am making "FirstView" & "SecondView".

//First View

    struct FirstView: View{

        var body: some View{

            ZStack{

                Rectangle()

                    .foregroundColor(.orange)

                Text("FirstView")

            }

        }

    }


//Second View


    struct SecondView: View{

        var body: some View{

            ZStack{

                Rectangle()

                    .foregroundColor(.yellow)

                Text("SecondView")

            }

        }

    }


These are just simple dummy views you can use your own views don't worry.

Step-3)

Now create a TabBarButton

    struct TabBarButton: View {

        let text: String

        @Binding var isSelected: Bool

        var body: some View {

            Text(text)

                .fontWeight(isSelected ? .heavy : .regular)

                .font(.custom("Avenir", size: 16))

                .padding(.bottom,10)

                .border(width: isSelected ? 2 : 1, edges: [.bottom], color: .black)

        }

    }


In the TabBarButton, two properties passing into the button.

  • text --> Which will show as Tab Text into the Tab Bar
  • isSelected --> Passing isSelected @Binding var for swapping the border to the button at the bottom side & swapping the .fontWeight

Here we are using the Text view and four modifiers are used onto the Text.


  • .fontWeight --> This is used for changing the font-weight of the Text
  • .font --> This is used for giving the Custom font and size to the Text
  • .padding --> This is used for giving the padding. Here we are giving the bottom padding. 
  • .border --> This is a custom modifier. You can see this in Step-5.


Step-4)


Now create a custom tab bar view


    struct CustomTopTabBar: View {

        @Binding var tabIndex: Int

        var body: some View {

            HStack(spacing: 20) {

                TabBarButton(text: "FirstView", isSelected: .constant(tabIndex == 0))

                    .onTapGesture { onButtonTapped(index: 0) }

                TabBarButton(text: "SecondView", isSelected: .constant(tabIndex == 1))

                    .onTapGesture { onButtonTapped(index: 1) }

                Spacer()

            }

            .border(width: 1, edges: [.bottom], color: .black)

        }

    

        private func onButtonTapped(index: Int) {

            withAnimation { tabIndex = index }

        }

    }


In the CustomTopTabBar view, we are passing the @Binding var tabIndex. This tabIndex is just controlling which tab is selected. Here we are using our TabBarButton two times its means that we have two tabs or two tab items. By default, the tabIndex will be "0". It means that the first tab item will be selected when the view appears or render the first time


Here we have made a function which is called onButtonTapped. This function taking an integer parameter and passing it to the tabIndex. When the value of tabIndex is changed then it will pass through the TabBarButton by using .constant(tabIndex == 0) or .constant(tabIndex == 1). If the tabIndex is not equal to "0" then .constant(tabIndex == 0) function will return the false and then the "0" tabIndex will be unselected and if the tabIndex is equal to "1" then .constant(tabIndex == 1) function will return true, then "1" tabIndex will be selected. It means we just passing the boolean value into the isSelected property into the TabBarButton


Step-5)


Now create an EdgerBorder view


struct EdgeBorder: Shape {


    var width: CGFloat

    var edges: [Edge]


    func path(in rect: CGRect) -> Path {

        var path = Path()

        for edge in edges {

            var x: CGFloat {

                switch edge {

                case .top, .bottom, .leading: return rect.minX

                case .trailing: return rect.maxX - width

                }

            }


            var y: CGFloat {

                switch edge {

                case .top, .leading, .trailing: return rect.minY

                case .bottom: return rect.maxY - width

                }

            }


            var w: CGFloat {

                switch edge {

                case .top, .bottom: return rect.width

                case .leading, .trailing: return self.width

                }

            }


            var h: CGFloat {

                switch edge {

                case .top, .bottom: return self.width

                case .leading, .trailing: return rect.height

                }

            }

            path.addPath(Path(CGRect(x: x, y: y, width: w, height: h)))

        }

        return path

    }

}


EdgeBorder is a view that will use for giving the border to any view on a specific side. For using this view as a modifier, then we will write the View extension as you can see below.


extension View {

    func border(width: CGFloat, edges: [Edge], color: SwiftUI.Color) -> some View {

        overlay(EdgeBorder(width: width, edges: edges).foregroundColor(color))

    }

}


Hope you understand. Thanks for reading this :) If you have any issues then comment below.


Complete Code:


import SwiftUI

import SDWebImageSwiftUI


struct ContentView: View {

    

    @State var tabIndex = 0

    

    var body: some View {

        VStack{

            CustomTopTabBar(tabIndex: $tabIndex)

            if tabIndex == 0 {

                FirstView()

            }

            else {

                SecondView()

            }

            Spacer()

        }

        .frame(width: UIScreen.main.bounds.width - 24, alignment: .center)

        .padding(.horizontal, 12)

    }

}


struct FirstView: View{

    var body: some View{

        ZStack{

            Rectangle()

                .foregroundColor(.orange)

            Text("FirstView")

        }

    }

}


struct SecondView: View{

    var body: some View{

        ZStack{

            Rectangle()

                .foregroundColor(.yellow)

            Text("SecondView")

        }

    }

}


struct CustomTopTabBar: View {

    @Binding var tabIndex: Int

    var body: some View {

        HStack(spacing: 20) {

            TabBarButton(text: "FirstView", isSelected: .constant(tabIndex == 0))

                .onTapGesture { onButtonTapped(index: 0) }

            TabBarButton(text: "SecondView", isSelected: .constant(tabIndex == 1))

                .onTapGesture { onButtonTapped(index: 1) }

            Spacer()

        }

        .border(width: 1, edges: [.bottom], color: .black)

    }

    

    private func onButtonTapped(index: Int) {

        withAnimation { tabIndex = index }

    }

}


struct TabBarButton: View {

    let text: String

    @Binding var isSelected: Bool

    var body: some View {

        Text(text)

            .fontWeight(isSelected ? .heavy : .regular)

            .font(.custom("Avenir", size: 16))

            .padding(.bottom,10)

            .border(width: isSelected ? 2 : 1, edges: [.bottom], color: .black)

    }

}


struct EdgeBorder: Shape {


    var width: CGFloat

    var edges: [Edge]


    func path(in rect: CGRect) -> Path {

        var path = Path()

        for edge in edges {

            var x: CGFloat {

                switch edge {

                case .top, .bottom, .leading: return rect.minX

                case .trailing: return rect.maxX - width

                }

            }


            var y: CGFloat {

                switch edge {

                case .top, .leading, .trailing: return rect.minY

                case .bottom: return rect.maxY - width

                }

            }


            var w: CGFloat {

                switch edge {

                case .top, .bottom: return rect.width

                case .leading, .trailing: return self.width

                }

            }


            var h: CGFloat {

                switch edge {

                case .top, .bottom: return self.width

                case .leading, .trailing: return rect.height

                }

            }

            path.addPath(Path(CGRect(x: x, y: y, width: w, height: h)))

        }

        return path

    }

}


extension View {

    func border(width: CGFloat, edges: [Edge], color: SwiftUI.Color) -> some View {

        overlay(EdgeBorder(width: width, edges: edges).foregroundColor(color))

    }

}



Screenshot:




Hope you understand, how to make a custom top tab bar. Thanks :) 😀If you have any issues then comment below.

avatar
AUTHOR: Muhammad Abbas
I am Software Engineer.

0 Comments