在 SwiftUI 中,当某个视图包含多个子视图,并且这些子视图都设置了同一类型的 > PreferenceKey 时,父视图会自动将这些子视图的偏好值(Preference Values)
合并为一个单一的值,并向上传递给更上层的祖先视图
在 SwiftUI 中,数据通常通过向下传递(父 → 子)实现,例如使用 @State、@Binding 或 @Environment。但某些场景需要子视图向上传递数据,例如:
1、 获取子视图的尺寸或位置(如实现滚动视图跟踪元素位置)。
2、 收集多个子视图的信息(如动态调整父视图布局)。
3、 传递自定义状态(如子视图完成加载后通知父视图)。
PreferenceKey 提供了一种声明式的解决方案,无需直接引用子视图或管理复杂回调。
import SwiftUI
struct ContentView: View {
@State private var text:String = "hellow world"
var body: some View {
VStack{
ChildView()
}
.onPreferenceChange(CustomMessageKey.self) { newValue in
print(newValue)
}
}
}
// 示例:传递一个字符串数据
struct CustomMessageKey: PreferenceKey {
// 默认值(必须实现)
static var defaultValue: String = ""
// 合并多个子视图传递的值(必须实现)
static func reduce(value: inout String, nextValue: () -> String) {
value = nextValue() // 这里取最后一个子视图的值
// 若需要合并所有子视图的值,可改为:value += nextValue()
}
}
struct ChildView: View {
var body: some View {
Text("我是子视图")
// 发送数据(例如:固定字符串)
.preference(key: CustomMessageKey.self, value: "Hello from Child!")
}
}
- 首先需要创建一个遵循 PreferenceKey 协议的类型,并实现两个必要方法:
- 用 .preference(key:value:) 修饰符将数据附加到子视图发送数据给父视图
- 通过 .onPreferenceChange(_:perform:) 在父视图中监听数据变化:
经常会使用NavigationView navigationTitle(_:)设置导航条标题, 修饰符本质上是通过 SwiftUI 内部的 PreferenceKey 机制将标题传递给 NavigationView。
struct ContentView: View {
var body: some View {
NavigationView {
Text("测试")
.navigationTitle("text")
.navigationBarTitleDisplayMode(.inline)
}
}
}