SwiftUI视图间数据传递的onPreferenceChange
SwiftUI视图间数据传递的onPreferenceChange

SwiftUI视图间数据传递的onPreferenceChange

onPreferenceChange 是一种在 SwiftUI 中使用偏好键(PreferenceKey)进行视图间数据传递的机制,通常用于监听子视图的某些动态变化并在父视图中作出响应。

基础语法

.onPreferenceChange(PreferenceKeyType.self) { newValue in
    // 响应偏好值的变化
}

PreferenceKeyType.self:表示要监听的偏好值的类型,必须是一个遵循 PreferenceKey 协议的类型。

newValue:回调中接收到的最新值。

作用范围:仅监听作用域内的子视图设置的偏好值变化。

完整使用流程

PreferenceKey协议为

public protocol PreferenceKey {
    associatedtype Value
    static var defaultValue: Self.Value { get }
    static func reduce(value: inout Self.Value, nextValue: () -> Self.Value)
}

1、定义一个 PreferenceKey

必须定义一个 PreferenceKey 类型,用来存储偏好值。

struct CustomPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0 // 默认值

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        // 合并多个值的逻辑(默认取最新值)
        value = nextValue()
    }
}

defaultValue:定义偏好值的初始值。

reduce:定义合并逻辑(如果有多个子视图设置值)。

2、在子视图设置偏好值

使用 preference(key:value:) 方法,将数据传递给父视图。

GeometryReader { geo in
    Color.clear
        .preference(key: CustomPreferenceKey.self, value: geo.frame(in: .global).minY)
}

作用:传递 geo.frame(in: .global).minY 的值给 CustomPreferenceKey。

3、在父视图监听偏好值

使用 onPreferenceChange 监听偏好值变化:

.onPreferenceChange(CustomPreferenceKey.self) { newValue in
    print("New Value: \(newValue)")
    // 在此响应偏好值变化
}

每次子视图的偏好值发生变化时,父视图会接收到最新值并触发回调。

代码示例

场景 1:监听滚动位置

在滚动视图中,监听每个子视图的相对位置。

struct ScrollOffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

struct ContentView: View {
    @State private var scrollOffset: CGFloat = 0

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                ForEach(0..<10, id: \.self) { index in
                    Text("Item \(index)")
                        .frame(width: 300, height: 150)
                        .background(Color.orange)
                        .cornerRadius(10)
                        .padding()
                        .background(GeometryReader { geo in
                            Color.clear.preference(key: ScrollOffsetPreferenceKey.self, value: geo.frame(in: .global).minY)
                        })
                }
            }
        }
        .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
            scrollOffset = value
            print("Scroll Offset: \(value)")
        }
    }
}

运行效果

子视图的 frame 相对于全局坐标系的位置变化时,父视图可以通过 onPreferenceChange 获取更新值。

可以根据滚动位置动态调整视图内容。

场景 2:动态调整视图背景

根据子视图的位置动态改变父视图背景颜色。

struct BackgroundColorPreferenceKey: PreferenceKey {
    static var defaultValue: Color = .white
    static func reduce(value: inout Color, nextValue: () -> Color) {
        value = nextValue()
    }
}

struct ContentView: View {
    @State private var backgroundColor: Color = .white

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                ForEach(0..<5, id: \.self) { index in
                    Text("Item \(index)")
                        .frame(width: 300, height: 150)
                        .background(index % 2 == 0 ? Color.orange : Color.blue)
                        .cornerRadius(10)
                        .preference(key: BackgroundColorPreferenceKey.self, value: index % 2 == 0 ? Color.orange : Color.blue)
                }
            }
        }
        .onPreferenceChange(BackgroundColorPreferenceKey.self) { newColor in
            backgroundColor = newColor
        }
        .background(backgroundColor)
    }
}

运行效果

滚动视图时,当前子视图的背景颜色会被赋值到父视图背景。

注意事项

1、子视图到父视图

onPreferenceChange 只能从子视图向父视图传递值。父视图不能直接修改子视图的状态。

2、多个值的合并

使用 PreferenceKey 时,多个子视图的值会被合并,通过实现 reduce 方法定义合并逻辑。

3、性能考虑

如果有大量子视图传递数据,可能会引发性能问题,应尽量优化合并逻辑。

总结

onPreferenceChange 提供了一种灵活的方式,让父视图能够感知子视图的动态变化,在需要动态布局、监听滚动或几何变化时非常有用,是 SwiftUI 开发的重要工具之一。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注