iOS18 邮件TabBar效果
iOS18改版的邮件 App tabBar 的联动效果。


导航栏框架

- 滚动视图 垂直布局
- 基于滚动的几何变化 修饰符:
onScrollGeometryChange
- 实现可搜索功能,并设置了导航栏抽屉:
navigationBarDrawer
- 自定义Mail 的 tabBar
自定义 Mail 的 tabBar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| struct CustomTabbar: View { @Binding var activeTab: TabModel @Environment(\.colorScheme) private var scheme
var body: some View { GeometryReader { _ in HStack(spacing: 8) { HStack(spacing: activeTab == .allMails ? -15 : 8) { ForEach(TabModel.allCases.filter({ $0 != .allMails }), id: \.rawValue) { tab in ResizableButton(tab) } } if activeTab == .allMails { ResizableButton(.allMails) .transition(.offset(x: 200)) } } .padding(.horizontal, 15) } .frame(height: 50) } @ViewBuilder func ResizableButton(_ tab: TabModel) -> some View { HStack(spacing: 8) { Image(systemName: tab.symbolImage) .opacity(activeTab == tab ? 0 : 1) .overlay { Image(systemName: tab.symbolImage) .symbolVariant(.fill) .opacity(activeTab == tab ? 1 : 0) } if tab == activeTab { Text(tab.rawValue) .font(.callout) .fontWeight(.semibold) .lineLimit(1) } } .foregroundStyle(tab == .allMails ? schemeColor : tab == activeTab ? .white : .gray) .frame(maxHeight: .infinity) .padding(.horizontal, activeTab == tab ? 13 : 20) .background { Rectangle() .fill(activeTab == tab ? tab.color : .inActiveTab) } .clipShape(.rect(cornerRadius: 20, style: .continuous)) .background { RoundedRectangle(cornerRadius: 20, style: .continuous) .fill(.background) .padding(activeTab == .allMails && tab != .allMails ? -3:3) } .contentShape(.rect) .onTapGesture { guard tab != .allMails else { return } withAnimation(.bouncy(duration: 0.3)) { if tab == activeTab { activeTab = .allMails } else { activeTab = tab } } } }
|
系统导航栏自定义
1 2 3 4 5 6 7 8 9 10 11 12
| init() { let appearance = UINavigationBarAppearance() appearance.configureWithTransparentBackground() appearance.shadowColor = .clear appearance.backgroundColor = .clear UINavigationBar.appearance().standardAppearance = appearance UINavigationBar.appearance().scrollEdgeAppearance = appearance }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| CustomTabbar(activeTab: $activeTab) .frame(height: isSearchActive ? 0 : nil, alignment: .top) .opacity(isSearchActive ? 0 : 1) .padding(.bottom, 10) .background { let progress = min(max((scrollOffset + startTopInset - 110) / 15, 0),1) ZStack(alignment: .bottom) { Rectangle() .fill(.ultraThinMaterial)
Rectangle() .fill(.gray.opacity(0.3)) .frame(height: 1) } .padding(.top, -topInset) .opacity(progress) } .offset(y: (scrollOffset + topInset) > 0 ? (scrollOffset + topInset) : 0) .zIndex(1000)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @State private var topInset: CGFloat = 0 @State private var startTopInset: CGFloat = 0 @State private var scrollOffset: CGFloat = 0 ScrollView(.vertical) {} .onScrollGeometryChange(for: CGFloat.self, of: { $0.contentOffset.y }, action: { oldValue, newValue in scrollOffset = newValue }) .onScrollGeometryChange(for: CGFloat.self, of: { $0.contentInsets.top }, action: { oldValue, newValue in if startTopInset == .zero { startTopInset = newValue } topInset = newValue })
|
1 2 3 4 5
| @Environment(\.colorScheme) private var scheme
var schemeColor: Color { return scheme == .dark ? .black : .white }
|
总结
一、核心功能模块
1 2 3 4 5 6 7 8 9 10 11
| // 滚动响应系统 struct ScrollResponseConfig { let collapseThreshold: CGFloat = 50 let transitionSpeed: CGFloat = 15 }
// 视图状态管理 class ViewStateManager: ObservableObject { @Published var searchPhase: SearchPhase = .inactive @Published var headerCollapseProgress: CGFloat = 0 }
|
二、视觉层级架构
三、关键技术实现
- 混合滚动检测:通过几何代理同时捕捉:
1 2 3 4 5 6 7
| .onScrollGeometryChange(for: CGFloat.self, of: { $0.contentInsets.top // 监测安全区域变化 }) .onScrollGeometryChange(for: CGFloat.self) { $0.contentOffset.y // 监测滚动位移 }
|
- 动效曲线控制:使用分段式进度计算
1
| let progress = min(max((scrollOffset + startTopInset - 50) / 15, 0), 1)
|
- 布局优化方案:
- Z轴分层管理
(.zIndex(1000))
- 条件高度约束
(.frame(height: isSearchActive ? 0 : nil))
- 异步布局更新
(.animation(.interactiveSpring, value: isSearchActive))
四、兼容性处理
- 导航栏透明化适配:
1
| UINavigationBar.appearance().configureWithTransparentBackground()
|
- 多机型安全区域适配:
1
| .padding(.top, -topInset) // 抵消系统顶部留白
|
- 深色模式适配方案:
1 2
| @Environment(\.colorScheme) private var scheme Color.adaptiveBackground(for: scheme)
|