onPreferenceChange的作用是让父视图能够监听子视图通过偏好键传递的数据变化,并在数据变化时执行相应的操作,常用于跨视图层级的状态同步,如布局调整、动画触发等场景
1.偏好键(PreferenceKey):需要定义一个遵循 PreferenceKey 协议的类型,指定需要传递的数据类型和合并策略。
reduce方法:用于合并多个子视图传递的偏好值(例如,在容器视图中收集所有子视图的数据)。- 示例:
struct MyPreferenceKey: PreferenceKey {
typealias Value = CGSize // 传递的数据类型
static var defaultValue: Value = .zero // 默认值
// 合并子视图传递的值(如多个子视图设置同一偏好键)
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue() // 这里可以自定义合并逻辑(如累加、取最大等)
}
}
2. onPreferenceChange 的工作流程
- 子视图设置偏好值:
子视图通过.preference(key:value:)修饰符设置数据。
ChildView()
.preference(key: MyPreferenceKey.self, value: CGSize(width: 100, height: 50))
- 父视图监听变化:
父视图通过.onPreferenceChange(_:perform:)监听指定偏好键的变化。
ParentView()
.onPreferenceChange(MyPreferenceKey.self) { newSize in
print("子视图传递的尺寸变化为:\(newSize)")
// 更新父视图状态或触发其他逻辑
}
3. 对比其他数据流方式
@State和@Binding:适用于父子视图直接传递数据,但无法跨多层级。
父视图(ParentView)使用@State管理私有状态,并通过$符号将状态转换为Binding传递给子视图:
struct ParentView: View {
// 父视图使用 @State 管理状态
@State private var isToggleOn: Bool = false
var body: some View {
VStack {
Text("父视图状态:\(self.isToggleOn ? "开" : "关")")
.font(.title)
// 将 isToggleOn 的 Binding 传递给子视图
ChildView(isToggleOn: $isToggleOn)
}
.padding()
.background(Color.yellow.opacity(0.2))
.cornerRadius(6)
}
}
子视图(ChildView)使用@Binding 接收父视图的绑定,并可以修改父视图的状态:
struct ChildView: View {
// 子视图通过 @Binding 接收绑定
@Binding var isToggleOn: Bool
var body: some View {
VStack {
// 修改绑定值会直接更新父视图的 @State
Toggle("子视图开关", isOn: $isToggleOn)
.padding()
Text("子视图显示:\(self.isToggleOn ? "开" : "关")")
}
.padding()
.background(Color.gray.opacity(0.2))
.cornerRadius(10)
}
}
@EnvironmentObject:全局状态共享,可能过度耦合。
使用步骤:
(1)定义可观察对象
创建一个遵循ObservableObject协议的类,并通过@Published标记需要触发视图更新的属性:
// 定义数据模型
class UserSettings: ObservableObject {
@Published var isLoggedIn: Bool = false
@Published var username: String = "Guest"
}
(2)注入环境对象
在视图层级的最顶层(如根视图)通过.environmentObject()修饰符注入实例:
struct ContentView: View {
// 创建实例(通常由上层如SceneDelegate或App结构体注入)
let settings = UserSettings()
var body: some View {
NavigationStack {
HomeView()
.environmentObject(settings) // 注入到环境
}
}
}
(3)在子视图中访问。
在任意子视图中通过@EnvironmentObject获取共享对象:
struct HomeView: View {
// 直接从环境中获取 UserSettings 实例
@EnvironmentObject var settings: UserSettings
var body: some View {
VStack {
Text("当前用户: \(settings.username)")
Button("登录") {
settings.isLoggedIn = true
settings.username = "Admin"
}
}
}
}
onPreferenceChange:轻量级、精准监听子视图数据变化,适合跨层级且无需显式绑定的场景。