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一致:
- 自定义底部TabBar:在主ViewController的底部添加一个自定义UIView作为TabBar,用UIButton实现Tab项,去掉系统UITabBar的默认动画;
- 配置SheetPresentationController:使用
UISheetPresentationController,添加自定义的UISheetPresentationController.Detent,计算好最小高度(屏幕高度 - TabBar高度); - 样式调整:给Sheet添加模糊背景(
UIBlurEffect),设置顶部圆角,确保TabBar始终在Sheet下方可见。
内容来源于stack exchange
相关产品推荐
相关产品推荐

