扬庆の博客

SwiftUI-iOS18MailsTabbar动态效果

字数统计: 845阅读时长: 4 min
2025/07/13 Share

iOS18 邮件TabBar效果

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

  • 收拢效果《显示所有邮件》

CleanShot 2025-07-07 at 15.44.49@2x.png

  • 展开效果 《 非所有邮件》

CleanShot 2025-07-07 at 15.45.38@2x.png

导航栏框架

CleanShot 2025-07-07 at 16.23.23@2x.png

  • 滚动视图 垂直布局
  • 基于滚动的几何变化 修饰符: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 } // all mails 点击没反应

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
/// 描述当关联的 UIScrollView 到达与导航栏相邻的边缘(导航栏的顶部边缘)时,
/// 导航栏要使用的外观属性。如果未设置,则将改为使用修改后的标准外观。
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}
  • Mail 的 tabBar 和导航栏联接一起
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// tabBar
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
/// Scroll Properties
@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. 混合滚动检测:通过几何代理同时捕捉:
1
2
3
4
5
6
7
.onScrollGeometryChange(for: CGFloat.self, of: { 
$0.contentInsets.top // 监测安全区域变化
})

.onScrollGeometryChange(for: CGFloat.self) {
$0.contentOffset.y // 监测滚动位移
}
  1. 动效曲线控制:使用分段式进度计算
1
let progress = min(max((scrollOffset + startTopInset - 50) / 15, 0), 1)
  1. 布局优化方案:
    • Z轴分层管理(.zIndex(1000))
    • 条件高度约束(.frame(height: isSearchActive ? 0 : nil))
    • 异步布局更新(.animation(.interactiveSpring, value: isSearchActive))

四、兼容性处理

  1. 导航栏透明化适配:
1
UINavigationBar.appearance().configureWithTransparentBackground()
  1. 多机型安全区域适配:
1
.padding(.top, -topInset) // 抵消系统顶部留白
  1. 深色模式适配方案:
1
2
@Environment(\.colorScheme) private var scheme
Color.adaptiveBackground(for: scheme)
CATALOG
  1. 1. iOS18 邮件TabBar效果
    1. 1.0.1. 导航栏框架
    2. 1.0.2. 自定义 Mail 的 tabBar
    3. 1.0.3. 系统导航栏自定义
    4. 1.0.4. 总结
      1. 1.0.4.1. 一、核心功能模块
      2. 1.0.4.2. 三、关键技术实现
      3. 1.0.4.3. 四、兼容性处理