SwiftUI笔记之onPreferenceChange

301 阅读2分钟

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轻量级、精准监听子视图数据变化,适合跨层级且无需显式绑定的场景。