You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

SwiftUI中如何实现带切换按钮的玻璃态TabView,通过切换按钮切换不同标签页组?

SwiftUI中如何实现带切换按钮的玻璃态TabView,通过切换按钮切换不同标签页组?

嘿,我完全get到你的需求了——就是要一个带玻璃质感的Tab栏,还得在同一行加个切换按钮,点一下就能在两组不同的标签页之间无缝切换对吧?这绝对可以实现,我给你捋清楚思路,再上完整的代码示例:

核心思路

其实核心就是用状态变量控制当前显示哪一组标签,再手动实现一个带玻璃态的底部栏,把切换按钮和对应组的标签项整合在一起,最后根据状态显示对应组的内容视图就行。不用纠结原生TabView的search角色按钮,自定义切换按钮反而更灵活,完全贴合你的需求。

完整实现代码

import SwiftUI

struct DualGroupTabView: View {
    // 控制当前显示哪一组标签
    @State private var isShowingFirstGroup = true
    // 第一组的选中标签索引
    @State private var selectedFirstTab = 0
    // 第二组的选中标签索引
    @State private var selectedSecondTab = 0
    
    var body: some View {
        VStack(spacing: 0) {
            // 内容显示区域
            if isShowingFirstGroup {
                // 第一组的TabView,支持滑动切换
                TabView(selection: $selectedFirstTab) {
                    Text("Star 1 内容页")
                        .tag(0)
                        .background(Color.white)
                    Text("Gear 1 内容页")
                        .tag(1)
                        .background(Color.gray.opacity(0.1))
                }
                .tabViewStyle(.page(indexDisplayMode: .never))
            } else {
                // 第二组的TabView
                TabView(selection: $selectedSecondTab) {
                    Text("Star 2 内容页")
                        .tag(0)
                        .background(Color.blue.opacity(0.1))
                    Text("Gear 2 内容页")
                        .tag(1)
                        .background(Color.purple.opacity(0.1))
                }
                .tabViewStyle(.page(indexDisplayMode: .never))
            }
            
            // 底部玻璃态标签栏
            HStack(spacing: 0) {
                // 组切换按钮
                Button {
                    isShowingFirstGroup.toggle()
                    // 切换组时重置选中标签到第一个,避免内容和标签不匹配
                    if isShowingFirstGroup {
                        selectedFirstTab = 0
                    } else {
                        selectedSecondTab = 0
                    }
                } label: {
                    Image(systemName: isShowingFirstGroup ? "arrowshape.right.arrowshape.left" : "arrowshape.left.arrowshape.right")
                        .foregroundColor(.primary)
                        .padding(.horizontal, 16)
                        .padding(.vertical, 12)
                }
                
                Spacer()
                
                // 根据当前组显示对应的标签按钮
                if isShowingFirstGroup {
                    TabButton(icon: "list.star", isSelected: selectedFirstTab == 0) {
                        selectedFirstTab = 0
                    }
                    TabButton(icon: "gear", isSelected: selectedFirstTab == 1) {
                        selectedFirstTab = 1
                    }
                } else {
                    TabButton(icon: "list.star", isSelected: selectedSecondTab == 0) {
                        selectedSecondTab = 0
                    }
                    TabButton(icon: "gear", isSelected: selectedSecondTab == 1) {
                        selectedSecondTab = 1
                    }
                }
                
                Spacer()
            }
            .padding(.horizontal, 16)
            .padding(.vertical, 8)
            // 玻璃态背景,可选ultraThinMaterial/thinMaterial等不同质感
            .background(.ultraThinMaterial)
            .cornerRadius(16, corners: [.topLeft, .topRight])
            // 轻微阴影,提升玻璃态层次感
            .shadow(color: .black.opacity(0.08), radius: 6, x: 0, y: -2)
        }
        .ignoresSafeArea(.all, edges: .bottom)
    }
}

// 自定义标签按钮,统一样式
struct TabButton: View {
    let icon: String
    let isSelected: Bool
    let action: () -> Void
    
    var body: some View {
        Button(action: action) {
            Image(systemName: icon)
                .foregroundColor(isSelected ? .accentColor : .primary)
                .padding(.horizontal, 20)
                .padding(.vertical, 10)
                // 选中时的高亮背景
                .background(isSelected ? Color.accentColor.opacity(0.12) : Color.clear)
                .cornerRadius(12)
        }
    }
}

#Preview {
    DualGroupTabView()
}

关键细节说明

  • 状态管理:用isShowingFirstGroup这个布尔变量控制显示哪一组,每组单独维护自己的选中标签索引,避免切换组时内容混乱
  • 玻璃态效果:通过.background(.ultraThinMaterial)实现,你还可以换成.thinMaterial/.thickMaterial来调整玻璃的模糊程度
  • 自定义标签按钮:把标签按钮封装成组件,统一处理选中/未选中样式,后续修改样式只要改这一处就行
  • 滑动切换支持:内容区域用原生TabView.page样式,保留了原生的滑动切换体验,和标签按钮的点击交互完全同步
  • 布局优化:底部栏加了顶部圆角和轻微阴影,让玻璃态栏和内容区域的过渡更自然,视觉上更有层次感

额外优化建议

如果想要更贴近原生TabView的标签动画,可以给TabButton的选中状态加上过渡动画,比如在TabButtonbody里加个.animation(.easeInOut, value: isSelected);另外,切换组的时候也可以给内容区域加上淡入淡出的过渡,只要在内容区域外面包个withAnimation或者加.transition(.opacity)就行。

这样实现出来的效果完全符合你的需求:玻璃质感的底部栏,同一行的切换按钮,点击就能在两组标签之间无缝切换,同时保留了原生TabView的核心功能~

火山引擎 最新活动