Search code examples
iosswiftuicurvetabviewtabitem

How to add a bottom curve in TabView in SwiftUI?


I just want a Bottom curve in center of my tabView but i am not able to access tabView shape property.

Final Image

This is what i want.

Note:

The curve should always remain in center. And the items should swap, which is already achieved in the given code.

import SwiftUI

struct DashboardTabBarView: View {

@State private var selection: String = "home"

struct Item {
    let title: String
    let color: Color
    let icon: String
}

@State var items = [
    Item(title: "cart", color: .red, icon: "cart"),
    Item(title: "home", color: .blue, icon: "house"),
    Item(title: "car", color: .green, icon: "car"),
]

var body: some View {

    TabView(selection: $selection) {
        ForEach(items, id: \.title) { item in // << dynamically !!
            item.color
                .tabItem {
                    Image(systemName: item.icon)
                    Text(item.title)
                }
        }
    }
    .onChange(of: selection) { title in // << reorder with centered item
        let target = 1
        if var i = items.firstIndex(where: { $0.title == title }) {
            if i > target {
                i += 1
            }
            items.move(fromOffsets: IndexSet(integer: target), toOffset: i)
        }
    }
}
}

Solution

  • Ok, actually we need to solve two problems here, first - find a height of tab bar, and second - correctly align view custom view with represented selected item over standard tab bar. Everything else is mechanics.

    Here is simplified demo. Tested with Xcode 14 / iOS 16.

    demo

    Main part:

    • a possible solution for problem #1
    struct TabContent<V: View>: View {
        @Binding var height: CGFloat
        @ViewBuilder var content: () -> V
    
        var body: some View {
            GeometryReader { gp in  // << read bottom edge !!
                content()
                    .onAppear {
                        height = gp.safeAreaInsets.bottom
                    }
                    .onChange(of: gp.size) { _ in
                        height = gp.safeAreaInsets.bottom
                    }
            }
        }
    }
    
    • a possible solution for problem #2
        // Just put customisation in z-ordered over TabView
        ZStack(alignment: .bottom) {
            TabView(selection: $selection) {
                // .. content here
            }
    
            TabSelection(height: tbHeight, item: selected)
        }
    
    
    struct TabSelection: View {
        let height: CGFloat
        let item: Item
        
        var body: some View {
            VStack {
                Spacer()
                Curve()    // put curve over tab bar !!
                    .frame(maxWidth: .infinity, maxHeight: height)
                    .foregroundColor(item.color)
            }
            .ignoresSafeArea()   // << push to bottom !!
            .overlay(
                // Draw overlay
                Circle().foregroundColor(.black)
                    .frame(height: height).aspectRatio(contentMode: .fit)
                    .shadow(radius: 4)
                    .overlay(Image(systemName: item.icon)
                        .font(.title)
                        .foregroundColor(.white))
                , alignment: .bottom)
        }
    }
    

    Test module is here