带有中心圆形按钮的自定义 TabBar SwiftUI

问题描述 投票:0回答:1

这就是我正在尝试做的:

Screenshot

屏幕截图是从 14 iPhone 上截取的。

Screenshot

我想像第一张图片一样管理布局,其中所有间距和圆间距都减少了。如何实现准确的 TabBar UI?

代码:-

import SwiftUI

struct TestReorderTabItemsPerSelection: View {

@State private var selection: String = "house"
@State private var tbHeight = CGFloat.zero

struct Item {
    let title: String
    let color: Color
    let icon: String
}
@State var items = [
    Item(title: "folder", color: .red, icon: "folder"),
    Item(title: "eraser", color: .red, icon: "eraser.fill"),
    Item(title: "cart", color: .red, icon: "cart"),
    Item(title: "house", color: .blue, icon: "house"),
    Item(title: "car", color: .green, icon: "car"),
]

var selected: Item {
    items.first { $0.title == selection } ?? items[0]
}

var body: some View {
    ZStack(alignment: .bottom) {
        TabView(selection: $selection) {
            ForEach(items, id: \.title) { item in
                TabContent(height: $tbHeight) {
                    item.color
                } .tabItem {
                        Image(systemName: item.icon)
                        Text(item.title)
                }
            }
        }
        TabSelection(height: tbHeight, item: selected)
    }
}

struct TabSelection: View {
    let height: CGFloat
    let item: Item
    
    var body: some View {
        VStack {
            Spacer()
            Curve()
                .frame(maxWidth: .infinity, maxHeight: height)
                .foregroundColor(item.color)
        }
        .ignoresSafeArea()
        .overlay(
            Circle().foregroundColor(.black)
                .frame(height: height).aspectRatio(contentMode: .fit)
                .shadow(radius: 4)
                .overlay(Image(systemName: item.icon)
                    .font(.title)
                    .foregroundColor(.white))
            , alignment: .bottom)
    }
}

struct TabContent<V: View>: View {
    @Binding var height: CGFloat
    @ViewBuilder var content: () -> V
    var body: some View {
        GeometryReader { gp in
            content()
                .onAppear {
                    height = gp.safeAreaInsets.bottom
                }
                .onChange(of: gp.size, {
                    height = gp.safeAreaInsets.bottom
                })
        }
    }
}

struct Curve: Shape {
    func path(in rect: CGRect) -> Path {
        let h = rect.maxY * 0.7
        return Path {
            $0.move(to: .zero)
            $0.addLine(to: CGPoint(x: rect.midX / 2.0, y: rect.minY))
            $0.addCurve(to: CGPoint(x: rect.midX, y: h), control1: CGPoint(x: rect.midX * 0.8, y: rect.minY), control2: CGPoint(x: rect.midX * 0.7, y: h))
            $0.addCurve(to: CGPoint(x: rect.midX * 3.0 / 2.0, y: rect.minY), control1: CGPoint(x: rect.midX * 1.3, y: h), control2: CGPoint(x: rect.midX * 1.2, y: rect.minY))
            $0.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
         }
      }
   }
}

 struct TestReorderTabItemsPerSelection_Previews: PreviewProvider {
 static var previews: some View {
    TestReorderTabItemsPerSelection()
   }
}
swiftui layout uitabbar tabbar
1个回答
0
投票

TabView
不提供控制选项卡项目之间的水平间距所需的灵活性。

要实现您想要的目标,您需要实现自定义选项卡栏。

幸运的是,您已经具备了大部分必要的功能,只需要进行一些调整即可。

这是一个粗略的尝试:

import SwiftUI

struct TabItem: Identifiable, Equatable {
    let id: UUID = UUID()
    let title: String
    let color: Color
    let icon: String
}

struct TestReorderTabItemsPerSelection: View {
    
    @State private var selectedTab: TabItem?
    
    private let tabHeight: CGFloat = 50
    private let tabSpacing: CGFloat = 5
    
    @State var items = [
        TabItem(title: "folder", color: .orange, icon: "folder"),
        TabItem(title: "eraser", color: .cyan, icon: "eraser.fill"),
        TabItem(title: "house", color: .blue, icon: "house"),
        TabItem(title: "car", color: .green, icon: "car")
    ]
    
    var body: some View {
        
        ZStack(alignment: .bottom) {
            VStack(spacing: tabSpacing) {
                
                selectedTab?.color
                    .ignoresSafeArea()
                
                HStack {
                    HStack {
                        TabItemView(item: items[0], selectedTab: $selectedTab, tabHeight: tabHeight)
                        TabItemView(item: items[1], selectedTab: $selectedTab, tabHeight: tabHeight)
                    }
                    
                    Spacer()
                        .frame(maxWidth: .infinity, alignment: .center)

                    HStack {
                        TabItemView(item: items[2], selectedTab: $selectedTab, tabHeight: tabHeight)
                        TabItemView(item: items[3], selectedTab: $selectedTab, tabHeight: tabHeight)
                    }
                }
                .padding(.horizontal, 10)
            }
            .onAppear {
                selectedTab = items[0]
            }
            
            TabSelection(item: selectedTab ?? items[0], height: 80, tabHeight: tabHeight)
                .offset(y: -tabSpacing)
        }
    }
}

struct TabItemView: View {
    
    let item: TabItem
    @Binding var selectedTab: TabItem?
    var tabHeight: CGFloat
    
    private var isSelected: Bool {
        selectedTab == item
    }
    var body: some View {
        
        Button {
            selectedTab = item
        } label: {
            VStack {
                Image(systemName: item.icon)
                    .imageScale(.large)
                    .symbolVariant(.fill)
                Text(item.title)
                    .font(.caption)
            }
            .foregroundStyle(isSelected ? item.color : .gray)
        }
        .frame(maxWidth: .infinity, maxHeight: tabHeight)
    }
}

struct TabSelection: View {
    
    //Parameters
    let item: TabItem
    let height: CGFloat
    var tabHeight: CGFloat
    
    //Body
    var body: some View {
        Circle()
            .foregroundColor(.black)
            .frame(maxWidth: .infinity, maxHeight: height)
            .aspectRatio(contentMode: .fit)
            .shadow(radius: 4)
            .overlay {
                Image(systemName: item.icon)
                    .font(.title)
                    .foregroundColor(.white)
            }
            .offset(y: -5) //shift circle up
            .background(alignment: .bottom) {
                Curve()
                    .frame(maxWidth: .infinity, maxHeight: height, alignment: .bottom)
                .foregroundColor(item.color)
                .offset(y: height - tabHeight )
            }
    }
}

struct Curve: Shape {
    func path(in rect: CGRect) -> Path {
        let h = rect.maxY * 0.7
        return Path {
            $0.move(to: .zero)
            $0.addLine(to: CGPoint(x: rect.midX / 2.0, y: rect.minY))
            $0.addCurve(to: CGPoint(x: rect.midX, y: h), control1: CGPoint(x: rect.midX * 0.8, y: rect.minY), control2: CGPoint(x: rect.midX * 0.7, y: h))
            $0.addCurve(to: CGPoint(x: rect.midX * 3.0 / 2.0, y: rect.minY), control1: CGPoint(x: rect.midX * 1.3, y: h), control2: CGPoint(x: rect.midX * 1.2, y: rect.minY))
            $0.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
        }
    }
}

struct TestReorderTabItemsPerSelection_Previews: PreviewProvider {
    static var previews: some View {
        TestReorderTabItemsPerSelection()
    }
}

enter image description here

可以通过将曲线制作为带有“唇形”的矩形来进一步完善这种整体方法,这样您的背景将是一个视图,从而无需将曲线的边缘与上面的颜色相匹配,以及所有跳舞的过程.

© www.soinside.com 2019 - 2024. All rights reserved.