Custom Top Tab Bar in IOS (Swift, SwiftUI)
Step-1)
Create an XCode project with SwiftUI. Look at the following screenshot.
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")
}
}
}
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.