いい感じのカスタムTabViewを作る

# 完成形
![画面収録_2023-05-09_23_38_16_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648522/e937fa3a-0f5d-0a53-9d97-2cb4cc2b327f.gif)

# コード全体
```swift
import SwiftUI

struct TabItem: Identifiable {
    var id = UUID()
    var text: String
    var icon: String
    var tab: Tab
    var color: Color
}

var tabItems = [
    TabItem(text: "Home", icon: "house", tab: .home, color: .teal),
    TabItem(text: "Explore", icon: "magnifyingglass", tab: .explore, color: .blue),
    TabItem(text: "Notification", icon: "bell", tab: .notifications, color: .red),
    TabItem(text: "Library", icon: "rectangle.stack", tab: .library, color: .pink),
]

enum Tab: Int, CaseIterable {
    case home
    case explore
    case notifications
    case library
}

struct TabPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

struct CustomTabView: View {
    @State var selectedTab: Tab = .home
    
    var body: some View {
        ZStack(alignment: .bottom) {
            Group {
                switch selectedTab {
                case .home:
                    Text("Home")
                case .explore:
                    Text("Explore")
                case .notifications:
                    Text("Notification")
                case .library:
                    Text("Library")
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            CustomTab(selectedTab: $selectedTab)
        }
    }
}

struct CustomTab: View {
    @Binding var selectedTab: Tab
    @State var color: Color = .teal
    @State var tabItemWidth: CGFloat = .zero
    
    var body: some View {
        HStack {
            buttons
        }
        .padding(.horizontal, 8)
        .padding(.top, 14)
        .frame(height: 88, alignment: .top)
        .background(
            .ultraThinMaterial,
            in: RoundedRectangle(cornerRadius: 34, style: .continuous)
        )
        .background(
            background
        )
        .overlay(
            overlay
        )
        .strokeStyle(cornerRadius: 34)
        .frame(maxHeight: .infinity, alignment: .bottom)
        .ignoresSafeArea()
    }
    
    var buttons: some View {
        ForEach(tabItems) { item in
            Button {
                withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
                    selectedTab = item.tab
                    color = item.color
                }
            } label: {
                VStack(spacing: 0) {
                    Image(systemName: item.icon)
                        .symbolVariant(.fill)
                        .font(.body.bold())
                        .frame(width: 44, height: 29)
                    Text(item.text)
                        .font(.caption2)
                        .lineLimit(1)
                }
                .frame(maxWidth: .infinity)
            }
            .foregroundStyle(selectedTab == item.tab ? .primary : .secondary)
            .blendMode(selectedTab == item.tab ? .overlay : .normal)
            .overlay(
                GeometryReader { proxy in
                    Color.clear.preference(key: TabPreferenceKey.self, value: proxy.size.width)
                }
            )
            .onPreferenceChange(TabPreferenceKey.self) { value in
                tabItemWidth = value
            }
        }
    }
    
    var background: some View {
        HStack {
            ForEach(0 ..< selectedTab.rawValue, id: \.self) { i in
                Spacer()
            }
            Circle().fill(color).frame(width: tabItemWidth)
            ForEach(0 ..< (Tab.allCases.count - selectedTab.rawValue - 1), id: \.self) { i in
                Spacer()
            }
        }
        .frame(maxWidth: .infinity)
        .padding(.horizontal, 8)
    }
    
    var overlay: some View {
        HStack {
            ForEach(0 ..< selectedTab.rawValue, id: \.self) { i in
                Spacer()
            }
            Capsule()
                .fill(color)
                .frame(width: 28, height: 5)
                .frame(width: tabItemWidth)
                .frame(maxHeight: .infinity, alignment: .top)
            ForEach(0 ..< (Tab.allCases.count - selectedTab.rawValue - 1), id: \.self) { i in
                Spacer()
            }
        }
        .padding(.horizontal, 8)
    }
}
```