SwiftUI 中的 onPreferenceChange
修饰符
onPreferenceChange
是 SwiftUI 中一个强大的工具,用于响应视图层次结构中传递的偏好值(preference)变化。它通常与 PreferenceKey
协议一起使用,实现父子视图之间的数据传递。
基本概念
onPreferenceChange
允许你在偏好值发生变化时执行某些操作,而不需要显式的数据绑定或状态管理。
基本用法
swift
struct MyView: View {
@State private var childSize: CGSize = .zero
var body: some View {
VStack {
Text("子视图尺寸: \(childSize.width) x \(childSize.height)")
ChildView()
.onPreferenceChange(SizePreferenceKey.self) { newSize in
childSize = newSize
}
}
}
}
struct ChildView: View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.preference(key: SizePreferenceKey.self, value: CGSize(width: 100, height: 100))
}
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
实际应用场景
1. 获取视图尺寸
swift
struct ContentView: View {
@State private var textSize: CGSize = .zero
var body: some View {
VStack {
Text("Hello, World!")
.background(GeometryReader { geometry in
Color.clear
.preference(key: TextSizeKey.self, value: geometry.size)
})
Text("文本尺寸: \(textSize.width) x \(textSize.height)")
}
.onPreferenceChange(TextSizeKey.self) { size in
textSize = size
}
}
}
struct TextSizeKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
2. 多个视图的偏好值聚合
swift
struct ContentView: View {
@State private viewRects: [CGRect] = []
var body: some View {
VStack {
ForEach(0..<5) { index in
Text("Item \(index)")
.padding()
.background(GeometryReader { geometry in
Color.clear
.preference(key: ViewRectsKey.self,
value: [geometry.frame(in: .global)])
})
}
}
.onPreferenceChange(ViewRectsKey.self) { rects in
viewRects = rects
}
}
}
struct ViewRectsKey: PreferenceKey {
static var defaultValue: [CGRect] = []
static func reduce(value: inout [CGRect], nextValue: () -> [CGRect]) {
value.append(contentsOf: nextValue())
}
}
与 GeometryReader 结合使用
onPreferenceChange
经常与 GeometryReader
结合使用来获取视图的几何信息:
swift
struct ScrollViewOffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
struct ScrollViewWithOffset: View {
@State private var offset: CGFloat = 0
var body: some View {
ScrollView {
VStack {
ForEach(0..<50) { i in
Text("Item \(i)")
.frame(maxWidth: .infinity)
.padding()
}
}
.background(GeometryReader { geometry in
Color.clear
.preference(
key: ScrollViewOffsetPreferenceKey.self,
value: geometry.frame(in: .named("scrollView")).minY
)
})
}
.coordinateSpace(name: "scrollView")
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in
offset = value
}
.overlay(
Text("当前偏移: \(offset)")
.padding(),
alignment: .bottom
)
}
}
注意事项
-
性能考虑:偏好值变化会触发视图更新,应避免频繁更新导致性能问题
-
PreferenceKey 的 reduce 方法:用于合并多个视图的偏好值,需要根据需求正确实现
-
使用场景:适合用于父视图需要收集子视图信息的场景,但不适合用于频繁更新的数据
-
与 onChange 的区别 :
onPreferenceChange
专门用于处理偏好值变化,而onChange
用于观察任意状态变化
onPreferenceChange
提供了一种声明式的方式来响应视图层次结构中的数据变化,是实现复杂布局和交互的强大工具。