一、 GeometryReader(几何读取器)
SwiftUI 中的 GeometryReader 是一个强大的布局工具,允许开发者根据父视图或屏幕的几何信息动态调整子视图的布局。
- 作用:读取父视图的尺寸、安全区域、坐标空间等信息,并将这些数据通过 GeometryProxy 传递给子视图。
- 注意: 频繁的读取可能几何信息导致性能问题,所以仅在必要时使用 GeometryReader,或结合 EquatableView 优化渲染。
例如下面这个例子,设置红色矩形占屏幕为2/3,蓝色占屏幕1/3
struct ContentView: View {
var body: some View {
HStack(spacing: 0){
Rectangle().fill(.red)
.frame(width: UIScreen.main.bounds.width * 0.66666)
Rectangle().fill(Color.blue)
}
}
}
运行时没有问题,当你需要旋转设备是横屏时,就不满足需求。这时我们就可以使用GeometryReader动态获取
struct ContentView: View {
var body: some View {
GeometryReader { gemetry in
HStack(spacing: 0){
Rectangle().fill(.red)
.frame(width: gemetry.size.width * 0.6666)
Rectangle().fill(Color.blue)
}
}
}
监听滚动内容的几何变化
struct ContentView: View {
@State private var scrollOffset: CGFloat = 0
var body: some View {
VStack {
Text("导航栏(偏移:\(scrollOffset))")
.background(scrollOffset < 0 ? Color.red : Color.blue)
ScrollViewOffsetReader(offsetY: $scrollOffset)
}
}
}
struct ScrollViewOffsetReader: View {
@Binding var offsetY: CGFloat
var body: some View {
ScrollView {
GeometryReader { proxy in
Color.clear
.preference(
key: ScrollOffsetPreferenceKey.self,
value: proxy.frame(in: .global).minY
)
}
.frame(height: 0)
LazyVStack { /* 内容 */ }
}
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
offsetY = value
}
}
}
- GeometryReader 的作用:
放置在 ScrollView 内容顶部,通过 proxy.frame(in: .global).minY 获取其在屏幕全局坐标系中的 Y 坐标。
当用户滚动时,ScrollView 的内容会移动,导致 GeometryReader 的全局 Y 坐标变化。 - PreferenceKey 的作用:
将 GeometryReader 的 Y 坐标值传递给父视图。 - onPreferenceChange:
监听 PreferenceKey 的变化,实时更新 offsetY
二、ScrollViewReader(滚动视图读取器)
在 SwiftUI 中,ScrollViewReader 是一个强大的工具,可以让你通过编程控制滚动视图的定位。
一个通过 ScrollViewProxy 实例实现编程式滚动的视图。使用代理的 scrollTo(_:anchor:) 方法可执行精确滚动操作。
示例实现
以下示例创建了一个包含 100 个视图(组成颜色渐变)的 ScrollView,以及两个按钮(一个在顶部,一个在底部)。顶部按钮通过 ScrollViewProxy 滚动到底部按钮,反之亦然:
@Namespace var topID
@Namespace var bottomID
var body: some View {
ScrollViewReader { proxy in
ScrollView {
// 顶部按钮(滚动到底部)
Button("Scroll to Bottom") {
withAnimation(.easeInOut) {
proxy.scrollTo(bottomID, anchor: .top)
}
}
.id(topID)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
// 内容区域
LazyVStack(spacing: 10) {
ForEach(0..<100) { index in
Text("Item \(index)")
.frame(maxWidth: .infinity, minHeight: 50)
.background(index % 2 == 0 ? Color.gray.opacity(0.2) : Color.white)
.cornerRadius(8)
.id(index) // 为每个项目添加唯一标识
}
}
.padding(.horizontal)
// 底部按钮(滚动到顶部)
Button("Top") {
proxy.scrollTo(topID, anchor: .top)
}
.id(bottomID)
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}