SwiftUI 中 onScrollGeometryChange 和 onGeometryChange 详解
SwiftUI 在过去几年里,一直有一个痛点:
获取布局信息太麻烦
尤其是:
- 获取 View 尺寸
- 获取滚动 offset
- 获取 frame
- 获取可见区域
以前通常需要:
GeometryReader
+ PreferenceKey
+ coordinateSpace
一堆黑魔法。
而从新一代 SwiftUI 开始,Apple 正在逐渐用:
- onGeometryChange
- onScrollGeometryChange
替代传统方案。
为什么 Apple 要新增这些 API
以前:
GeometryReader { proxy in
...
}
有很多问题:
- 会参与布局
- 容易撑满空间
- 很难控制
- 性能不理想
- PreferenceKey 写法复杂
- ScrollView offset 获取非常痛苦
Apple 后来发现:
开发者真正想要的,
只是“观察几何变化”
而不是:
创建一个特殊布局容器
因此诞生了:
.onGeometryChange()
.onScrollGeometryChange()
一、onGeometryChange
它是什么
onGeometryChange 用来:
监听 View 自身几何信息变化。
例如:
- size
- frame
- position
- safeAreaInsets
- bounds
等。
它可以理解成:
GeometryReader 的现代替代方案
基本用法
Text("Hello")
.onGeometryChange(for: CGSize.self) { geometry in
geometry.size
} action: { newSize in
print(newSize)
}
API 结构解析
1. for
指定最终监听的数据类型。
例如:
CGSize.self
CGFloat.self
CGRect.self
2. transform
{ geometry in
geometry.size
}
这里:
- geometry 类似 GeometryProxy
- 用来提取需要的数据
3. action
{ newValue in
}
当值发生变化时触发。
监听宽度变化
Color.red
.frame(height: 100)
.onGeometryChange(for: CGFloat.self) { geometry in
geometry.size.width
} action: { width in
print(width)
}
获取 frame
.onGeometryChange(for: CGRect.self) { geometry in
geometry.frame(in: .global)
} action: { frame in
print(frame)
}
这相当于以前复杂的:
GeometryReader + PreferenceKey
onGeometryChange 最大的意义
以前:
想观察布局
必须参与布局
现在:
终于可以:
观察布局
而不影响布局
这是非常大的变化。
二、onScrollGeometryChange
这是更高级的版本。
专门给 ScrollView 使用。
它是什么
用于监听:
ScrollView 的几何变化
例如:
- contentOffset
- contentSize
- visibleRect
- contentInsets
- containerSize
等。
为什么它非常重要
以前 SwiftUI 获取滚动 offset:
极其痛苦。
通常需要:
GeometryReader
+ PreferenceKey
+ coordinateSpace
+ offset计算
一堆黑魔法。
现在:
.onScrollGeometryChange()
官方终于支持。
基本用法
ScrollView {
...
}
.onScrollGeometryChange(for: CGFloat.self) { geometry in
geometry.contentOffset.y
} action: { oldValue, newValue in
print(newValue)
}
ScrollGeometry 中有什么
这里的 geometry 不是普通 GeometryProxy。
而是:
ScrollGeometry
它包含:
contentOffset
geometry.contentOffset
滚动偏移量。
最常用。
contentSize
geometry.contentSize
内容总大小。
containerSize
geometry.containerSize
ScrollView 可视区域大小。
visibleRect
geometry.visibleRect
当前可见区域。
contentInsets
geometry.contentInsets
包括:
- safeArea
- safeAreaPadding
- safeAreaInset
之后的最终 inset。
获取 Scroll Offset
@State private var scrollY: CGFloat = 0
ScrollView {
...
}
.onScrollGeometryChange(for: CGFloat.self) { geometry in
geometry.contentOffset.y
} action: { _, y in
scrollY = y
}
导航栏渐变
.opacity(min(scrollY / 100, 1))
这是很多 App Store 风格页面的核心。
检测是否滚到底
let isBottom =
geometry.visibleRect.maxY >= geometry.contentSize.height
视差滚动
以前很复杂。
现在只需要:
geometry.contentOffset
即可实现。
两者核心区别
| onGeometryChange | onScrollGeometryChange | |
|---|---|---|
| 监听对象 | 普通 View | ScrollView |
| geometry 类型 | GeometryProxy 风格 | ScrollGeometry |
| 获取 offset | 不方便 | 官方支持 |
| 获取 visibleRect | ❌ | ✅ |
| 获取 contentSize | ❌ | ✅ |
| 适合场景 | 普通布局监听 | 滚动监听 |
为什么 onScrollGeometryChange 更高级
因为:
ScrollView 并不是普通布局
它内部存在:
- viewport
- contentLayout
- clipping
- bouncing
- insets
普通 GeometryReader 很难正确获取。
因此 Apple 单独设计了:
ScrollGeometry
iOS 版本
onGeometryChange
iOS 16+
onScrollGeometryChange
iOS 18+
属于最新 API。
现代 SwiftUI 的趋势
Apple 正在逐渐废弃:
GeometryReader 黑魔法时代
转向:
声明式几何观察
包括:
- onGeometryChange
- onScrollGeometryChange
- scrollPosition
- scrollTargetLayout
- scrollTargetBehavior
- scrollVisibility
这一整套新体系。
最后
现在可以这样理解:
GeometryReader
创建一个特殊布局容器
onGeometryChange
观察普通 View 的布局变化
onScrollGeometryChange
观察 ScrollView 的滚动几何变化
这也是 SwiftUI 未来布局系统的重要方向:
观察布局
而不是参与布局