【swiftUI】实现自定义的底部TabBar组件

一、凹陷背景效果

  • TabBar背景在凸出按钮处呈现凹陷

  • 凹陷曲线平滑自然,使用贝塞尔曲线实现

  • 顶部暗色渐变模拟凹陷的深度

Path绘制

方法 参数 类型 说明
move(to:) point CGPoint 将绘制起点移动到指定位置
addLine(to:) point CGPoint 从当前位置到目标点添加直线
addQuadCurve(to:control:) to CGPoint 二次贝塞尔曲线终点
control CGPoint 控制点(影响曲线弯曲程度)
addCurve(to:control1:control2:) to CGPoint 三次贝塞尔曲线终点
control1 CGPoint 第一个控制点
control2 CGPoint 第二个控制点
addArc(center:radius:startAngle:endAngle:clockwise:) center CGPoint 圆弧中心点
radius CGFloat 圆弧半径
startAngle Angle 起始角度
endAngle Angle 结束角度
clockwise Bool true=顺时针,false=逆时针
addEllipse(in:) rect CGRect 椭圆的外接矩形
addRoundedRect(in:cornerSize:style:) rect CGRect 矩形区域
cornerSize CGSize 圆角大小
style RoundedCornerStyle 圆角样式(continuous/circular)
closeSubpath() - - 闭合当前子路径
addPath(_:transform:) path Path 添加另一个路径
transform CGAffineTransform 变换矩阵
Swift 复制代码
// MARK: - TabBar背景形状(带凹陷效果)
struct TabBarBackgroundShape: Shape {
    let selectedIndex: Int
    let tabCount: Int
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        let cornerRadius: CGFloat = 0
        let buttonWidth = (rect.width - 30) / CGFloat(tabCount)
        let selectedButtonLX = buttonWidth * CGFloat(selectedIndex) + 20  //button的start X
        let selectedButtonRX = buttonWidth * CGFloat(selectedIndex + 1) + 10 //button的end X
        let selectedButtonX = buttonWidth * CGFloat(selectedIndex) + buttonWidth / 2 + 15
        let Depth: CGFloat = 15
        
        // 从左上角开始
        path.move(to: CGPoint(x: 0, y: cornerRadius))
        
        // 左上角圆角
        path.addQuadCurve(
            to: CGPoint(x: cornerRadius, y: 0),
            control: CGPoint(x: 0, y: 0)
        )
        
        // 上边线,直到凹陷开始
        path.addLine(to: CGPoint(x: selectedButtonLX - Depth, y: 0))

        // 凹陷左侧曲线 - 平滑过渡
        path.addQuadCurve(
            to: CGPoint(x: selectedButtonLX , y:Depth),
            control: CGPoint(x: selectedButtonLX , y: 0)
        )
        
        // 凹陷底部 - 左侧到中间
        path.addQuadCurve(
            to: CGPoint(x: selectedButtonX, y: 10 + buttonWidth / 2),
            control: CGPoint(x: selectedButtonLX, y: 4 + buttonWidth / 2)
        )
        
        // 凹陷底部 - 中间到右侧
        path.addQuadCurve(
            to: CGPoint(x: selectedButtonRX, y:Depth),
            control: CGPoint(x: selectedButtonRX, y: 4 + buttonWidth / 2)
        )
        
        // 凹陷右侧曲线
        path.addQuadCurve(
            to: CGPoint(x: selectedButtonRX + Depth, y: 0),
            control: CGPoint(x: selectedButtonRX , y: 0)
        )
        
        // 继续上边线到右上角
        path.addLine(to: CGPoint(x: rect.width - cornerRadius, y: 0))
        
        // 右上角圆角
        path.addQuadCurve(
            to: CGPoint(x: rect.width, y: cornerRadius),
            control: CGPoint(x: rect.width, y: 0)
        )
        
        // 右边线
        path.addLine(to: CGPoint(x: rect.width, y: rect.height - cornerRadius))
        
        // 右下角圆角
        path.addQuadCurve(
            to: CGPoint(x: rect.width - cornerRadius, y: rect.height),
            control: CGPoint(x: rect.width, y: rect.height)
        )
        
        // 底边线
        path.addLine(to: CGPoint(x: cornerRadius, y: rect.height))
        
        // 左下角圆角
        path.addQuadCurve(
            to: CGPoint(x: 0, y: rect.height - cornerRadius),
            control: CGPoint(x: 0, y: rect.height)
        )
        
        // 左边线
        path.addLine(to: CGPoint(x: 0, y: cornerRadius))
        
        return path
    }
}

二、凸出圆形按钮

  • 当前选中页面的按钮显示为凸出的圆形

  • 圆形按钮带有渐变和阴影,增强立体感

  • 白色高光线增加光泽效果

Swift 复制代码
// MARK: - TabBar按钮
struct AdTabBarButton: View {
    let tabItem: TabItem
    let isSelected: Bool
    let action: () -> Void
    
    @State private var scale: CGFloat = 1.0
    @State private var offset: CGFloat = 0
    @State private var rotation: Double = 0
    
    var body: some View {
        Button(action: {
            // 触觉反馈
            let generator = UIImpactFeedbackGenerator(style: .soft)
            generator.impactOccurred()
            
            // 动画序列
            withAnimation(.spring(response: 0.3, dampingFraction: 0.5)) {
                scale = 1.3
                offset = -15
                rotation = 10
            }
            
            // 恢复
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
                withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
                    scale = 1.0
                    offset = isSelected ? -20 : 0
                    rotation = 0
                }
            }
            
            action()
        }) {
            VStack(spacing: 4) {
                ZStack {
                    if isSelected {
                        // 选中状态的凸出圆形背景
                        Circle()
                            .fill(
                                AppTheme.current.buttonPrimaryBackground
                            )
                            .stroke(Color.white, lineWidth: 2)
                            .frame(width: 60, height: 60)
                            .offset(y: -20)
                            .scaleEffect(scale)
                    }
                    
                    Image(systemName: tabItem.icon)
                        .font(.system(size: isSelected ? 28 : 22, weight: .medium))
                        .foregroundColor( .white)
                        .scaleEffect(isSelected ? scale : 1.0)
                        .offset(y: isSelected ? -20 + offset : 0)
                        .rotationEffect(.degrees(isSelected ? rotation : 0))
                }
                
                if !isSelected {
                    Text(tabItem.title)
                        .font(.system(size: 14))
                        .foregroundColor(Color.app.buttonPrimaryText)
                }
            }
        }
    }
}

三、自定义tabbar

Swift 复制代码
// MARK: - 自定义TabBar
struct AdvancedTabBar: View {
    @Binding var selectedTab: Int
    
    let tabItems: [TabItem]
    
    var body: some View {
        ZStack {
            // 背景形状 - 带凹陷效果的TabBar
            TabBarBackgroundShape(selectedIndex: selectedTab, tabCount: tabItems.count)
                .fill(
                    LinearGradient(
                        colors: Color.app.brandGradient,
                        startPoint: .topLeading,
                        endPoint: .bottomTrailing
                    )
                )
                .frame(height: 70)

            
            // 按钮容器
            HStack(alignment:.top, spacing: 0) {
                ForEach(0..<tabItems.count, id: \.self) { index in
                    AdTabBarButton(
                        tabItem: tabItems[index],
                        isSelected: selectedTab == index
                    ) {
                        withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
                            selectedTab = index
                        }
                    }
                    .frame(maxWidth: .infinity)
                }
            }
            .padding(.horizontal, 15)
        }
    }
}

四、使用Demo

  1. TabView
参数 类型 说明 默认值 可用版本
selection Binding<SelectionValue> 当前选中的标签页 nil iOS 13+
content () -> Content TabView的内容构建器 必填 iOS 13+
tabViewStyle TabViewStyle TabView的样式 automatic iOS 13+
indexViewStyle IndexViewStyle 索引视图样式 automatic iOS 14+
Swift 复制代码
// MARK: - Tab 数据模型
struct TabItem: Identifiable {
    let id = UUID()
    let icon: String
    let title: String
    var badgeCount: Int = 0
    var isSpecial: Bool = false
}

// MARK: - 主视图
struct AdvancedTabBarView: View {
    @State private var selectedTab = 0
    let tabItems: [TabItem] = [
        TabItem(icon: "house.fill", title: "首页"),
        TabItem(icon: "magnifyingglass", title: "搜索"),
        TabItem(icon: "plus", title: "添加"),
        TabItem(icon: "bell.fill", title: "通知"),
        TabItem(icon: "person.fill", title: "我的")
    ]
    var body: some View {
        VStack {
            // 内容区域
            TabView(selection: $selectedTab) {
                HomeView()
                    .tag(0)
                
                SearchView()
                    .tag(1)
                
                AddView()
                    .tag(2)
                
                NotificationsView()
                    .tag(3)
                
                ProfileView()
                    .tag(4)
            }
            .tabViewStyle(.page(indexDisplayMode: .never))
            // 自定义TabBar
            AdvancedTabBar(selectedTab: $selectedTab, tabItems: tabItems)
        }
        .edgesIgnoringSafeArea(.bottom)
    }
}
相关推荐
松叶似针19 小时前
Flutter三方库适配OpenHarmony【secure_application】— iOS 端原生模糊遮罩实现分析
flutter·ios·cocoa
vx-bot55566620 小时前
企业微信ipad协议的会话管理机制与本地同步策略
ios·企业微信·ipad
阿捏利1 天前
详解Mach-O(十三)Mach-O __TEXT
macos·ios·c/c++·mach-o
pop_xiaoli1 天前
effective-Objective-C 第三章阅读笔记
笔记·ios·objective-c
东坡肘子1 天前
祝大家马年新春快乐! -- 肘子的 Swift 周报 #123
人工智能·swiftui·swift
BatmanWayne1 天前
swift微调记录
微调·swift
游戏开发爱好者82 天前
完整教程:App上架苹果App Store全流程指南
android·ios·小程序·https·uni-app·iphone·webview
阿捏利2 天前
详解Mach-O(十四)Mach-O __DATA
macos·ios·c/c++·mach-o
追夢秋陽2 天前
Cocoa 使用NSCollectionView显示列表,数据不足布局异常处理
macos·objective-c·cocoa·swift·collectionview