You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 26:如何实现类似「查找」App的TabView/Sheet联动效果

iOS 26:如何实现类似「查找」App的TabView/Sheet联动效果

嘿,我最近也研究了iOS 26 Beta里「查找」App的这个新交互,确实和之前的实现逻辑不一样——原生TabView的液态玻璃切换动画完全没有,而且Sheet收起时底部的Tab栏全程可见,还能直接上滑召回Sheet。咱们一步步拆解怎么复刻这个效果,先从SwiftUI的实现说起:

核心思路拆解

首先得明确:「查找」App里的底部栏不是原生TabView,是完全自定义的TabBar;而Sheet则是利用了iOS 16+的presentationDetents特性,设置了自定义的最小展开高度,刚好让Sheet底部对齐自定义TabBar的顶部,从而实现两者的联动。


SwiftUI实现步骤

1. 自定义底部TabBar(替代原生TabView)

原生TabView的切换动画是系统自带的液态玻璃效果,不符合需求,所以我们自己写一个底部栏:

struct CustomTabBar: View {
    @Binding var selectedTab: Int
    let tabItems: [(title: String, icon: String)]
    
    var body: some View {
        HStack {
            ForEach(0..<tabItems.count, id: \.self) { index in
                Button {
                    selectedTab = index
                    // 直接切换,没有原生TabView的液态玻璃动画
                } label: {
                    VStack(spacing: 4) {
                        Image(systemName: tabItems[index].icon)
                        Text(tabItems[index].title)
                            .font(.caption)
                    }
                    .foregroundColor(selectedTab == index ? .blue : .gray)
                    .frame(maxWidth: .infinity)
                }
            }
        }
        .padding(.top, 8)
        .padding(.bottom, 12)
        .background(.bar)
        .cornerRadius(16, corners: [.topLeft, .topRight])
        .shadow(radius: 4)
    }
}

// 给View添加指定圆角的扩展
extension View {
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape(RoundedCorner(radius: radius, corners: corners))
    }
}

struct RoundedCorner: Shape {
    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners
    
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

这个自定义TabBar完全控制了样式和交互,去掉了原生的动画,还能自定义圆角、阴影,和「查找」App的风格一致。

2. 联动Sheet:自定义最小展开高度

接下来要让Sheet收起时不覆盖TabBar,这里用presentationDetents的自定义detent来实现:

struct ContentView: View {
    @State private var selectedTab = 0
    @State private var showSheet = true
    // 自定义TabBar的高度,后续用来计算Sheet的最小高度
    private let tabBarHeight: CGFloat = 60
    
    var body: some View {
        ZStack(alignment: .bottom) {
            // 主内容区域:根据选中的Tab切换页面
            Group {
                switch selectedTab {
                case 0:
                    Text("设备页面")
                case 1:
                    Text("物品页面")
                case 2:
                    Text("我的页面")
                default:
                    EmptyView()
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            
            // 底部自定义TabBar
            CustomTabBar(selectedTab: $selectedTab, tabItems: [
                (title: "设备", icon: "iphone"),
                (title: "物品", icon: "airtag"),
                (title: "我", icon: "person")
            ])
            .frame(height: tabBarHeight)
            .padding(.horizontal)
        }
        .sheet(isPresented: $showSheet) {
            // Sheet的内容视图
            SheetContent()
                // 设置Sheet的可展开状态:自定义最小高度 + 全屏高度
                .presentationDetents([
                    .custom(.small(height: tabBarHeight)),
                    .large
                ])
                // 系统级模糊背景,贴近原生效果
                .presentationBackground(.ultraThinMaterial)
                // Sheet顶部圆角
                .presentationCornerRadius(24)
                // 显示顶部抓手,提示可拖动
                .presentationDragIndicator(.visible)
        }
    }
}

struct SheetContent: View {
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                ForEach(1..<20) { index in
                    Text("Sheet内容行 \(index)")
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(.white.opacity(0.8))
                        .cornerRadius(12)
                }
            }
            .padding()
        }
    }
}

// 扩展PresentationDetent,实现自定义高度的detent
extension PresentationDetent {
    static func custom(_ detent: CustomDetent) -> Self {
        .custom(detent.id) { context in
            detent.height(in: context)
        }
    }
    
    enum CustomDetent: Identifiable {
        case small(height: CGFloat)
        
        var id: String {
            switch self {
            case .small: return "small"
            }
        }
        
        func height(in context: PresentationDetent.Context) -> CGFloat {
            switch self {
            case .small(let height):
                // 计算Sheet的最小高度:屏幕总高度 - TabBar高度
                // 这样Sheet收起时,底部刚好对齐TabBar的顶部,TabBar完全可见
                return context.maxHeight - height
            }
        }
    }
}

关键细节说明

  • 自定义Detent:通过context.maxHeight - tabBarHeight计算Sheet的最小高度,确保Sheet不会覆盖底部TabBar;
  • 背景与圆角:用.presentationBackground(.ultraThinMaterial)实现和系统一致的模糊背景,.presentationCornerRadius设置顶部圆角;
  • 交互保留:Sheet的抓手、上滑展开、下滑收起的原生交互都被保留,和「查找」App的体验一致。

UIKit实现思路

如果习惯用UIKit,核心逻辑和SwiftUI一致:

  1. 自定义底部TabBar:在主ViewController的底部添加一个自定义UIView作为TabBar,用UIButton实现Tab项,去掉系统UITabBar的默认动画;
  2. 配置SheetPresentationController:使用UISheetPresentationController,添加自定义的UISheetPresentationController.Detent,计算好最小高度(屏幕高度 - TabBar高度);
  3. 样式调整:给Sheet添加模糊背景(UIBlurEffect),设置顶部圆角,确保TabBar始终在Sheet下方可见。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 08:17:59