swiftuitabbarrectanglesrounded-cornerstabview

TabBar Customisation in SwiftUI


I want to customize the tab bar like the curved rectangle in the center but all i am able to do is added one image in the center. the border should come below the circle, tried so many ways but it didn't worked, hope someone would help me to get this.

Here I have tried:

enter image description here

What I am expecting:

enter image description here

TabBarView:

struct TabBarView: View {
    
    @State var selectedTab = 0
    
    var body: some View {
        ZStack(alignment: .bottom) {
                    TabView(selection: $selectedTab) {
                        HomeView()
                            .tag(0)

                        Search()
                            .tag(1)

                        Tickets()
                            .tag(2)

                        Profile()
                            .tag(3)

                        Settings()
                            .tag(4)
                    }

                    RoundedRectangle(cornerRadius: 25)
                        .frame(width: 350, height: 70)
                        .foregroundColor(.white)
                        .shadow(radius: 0.8)

                    Button {
                        selectedTab = 2
                    } label: {
                        CustomTabItem(imageName: "ticket", title: "Ticket", isActive: (selectedTab == 2))
                    }
                    .frame(width: 65, height: 65)
                    .background(Color.white)
                    .clipShape(Circle())
                    .shadow(radius: 0.8)
                    .offset(y: -50)

                    HStack {
                        ForEach(TabbedItems.allCases, id: \.self) { item in
                            if item != .ticket { // Exclude the center button
                                Button {
                                    selectedTab = item.rawValue
                                } label: {
                                    CustomTabItem(imageName: item.iconName, title: item.title, isActive: (selectedTab == item.rawValue))
                                }
                            }
                        }
                    }
                    .frame(height: 70)
                }
            }
        }

Extension:

extension TabBarView {
    func CustomTabItem(imageName: String, title: String, isActive: Bool) -> some View{
        HStack(alignment: .center,spacing: 22){
            Spacer()
            Image(imageName)
                .resizable()
                .renderingMode(.template)
                .foregroundColor(isActive ? .purple : .gray)
                .frame(width: 25, height: 25)
            Spacer()
        }
    }
}

Solution

  • You can do it by creating a Shape with the required form. I had a go, here's something that's close:

    struct TabBarShape: Shape {
        let insetRadius: CGFloat
        let cornerRadius = CGFloat(25)
        let insetCornerAngle = 45.0
    
        func path(in rect: CGRect) -> Path {
            var path = Path()
    
            // Start just below the top-left corner
            var x = rect.minX
            var y = rect.minY + cornerRadius
            path.move(to: CGPoint(x: x, y: y))
    
            // Add the rounded corner on the top-left corner
            x += cornerRadius
            path.addArc(
                center: CGPoint(x: x, y: y),
                radius: cornerRadius,
                startAngle: .degrees(180),
                endAngle: .degrees(270),
                clockwise: false
            )
            // Begin inset in middle, cutting into shape
            x = rect.midX - (2 * insetRadius)
            y = rect.minY + insetRadius
            path.addArc(
                center: CGPoint(x: x, y: y),
                radius: insetRadius,
                startAngle: .degrees(270),
                endAngle: .degrees(270 + insetCornerAngle),
                clockwise: false
            )
            // Add a half-circle to fit the button
            x = rect.midX
            y = rect.minY
            path.addArc(
                center: CGPoint(x: x, y: y),
                radius: insetRadius,
                startAngle: .degrees(90 + insetCornerAngle),
                endAngle: .degrees(90 - insetCornerAngle),
                clockwise: true
            )
            // Complete the inset with the second rounded corner
            x += (2 * insetRadius)
            y += insetRadius
            path.addArc(
                center: CGPoint(x: x, y: y),
                radius: insetRadius,
                startAngle: .degrees(270 - insetCornerAngle),
                endAngle: .degrees(270),
                clockwise: false
            )
            // Top-right corner
            x = rect.maxX - cornerRadius
            y = rect.minY + cornerRadius
            path.addArc(
                center: CGPoint(x: x, y: y),
                radius: cornerRadius,
                startAngle: .degrees(270),
                endAngle: .degrees(0),
                clockwise: false
            )
            // Bottom-right corner
            y = rect.maxY - cornerRadius
            path.addArc(
                center: CGPoint(x: x, y: y),
                radius: cornerRadius,
                startAngle: .degrees(0),
                endAngle: .degrees(90),
                clockwise: false
            )
            // Bottom-left corner
            x = rect.minX + cornerRadius
            path.addArc(
                center: CGPoint(x: x, y: y),
                radius: cornerRadius,
                startAngle: .degrees(90),
                endAngle: .degrees(180),
                clockwise: false
            )
            path.closeSubpath()
            return path
        }
    }
    

    Then you can use the custom shape in place of the RoundedRectangle that you were using before:

    // RoundedRectangle(cornerRadius: 25)
    TabBarShape(insetRadius: 30)
        .frame(width: 350, height: 70)
        .foregroundColor(.white)
        .shadow(color: Color(white: 0.8), radius: 6, x: 0, y: 3)
    
    Button {
        selectedTab = 2
    } label: {
        CustomTabItem(imageName: "ticket", title: "Ticket", isActive: (selectedTab == 2))
    }
    .frame(width: 50, height: 50)
    .background(Color.white)
    .clipShape(Circle())
    .shadow(radius: 0.8)
    .offset(y: -50)
    

    Here's how it looks, you'll probably want to tweak the inset a bit more:

    TabBarShape