SwiftUI 中的 PreferenceKey 协议

SwiftUI 中的 PreferenceKey 协议

PreferenceKey 是 SwiftUI 中用于在视图层次结构中传递数据的协议,它允许子视图向上传递信息给祖先视图,而不需要显式的数据绑定。

基本概念

PreferenceKey 定义了一种机制,使子视图能够向父视图传递数据,父视图可以通过 onPreferenceChange 修饰符来监听这些数据的变化。

协议要求

实现 PreferenceKey 需要满足两个要求:

swift 复制代码
public protocol PreferenceKey {
    associatedtype Value
    
    // 提供默认值
    static var defaultValue: Self.Value { get }
    
    // 合并多个视图传递的值
    static func reduce(value: inout Self.Value, nextValue: () -> Self.Value)
}

创建自定义 PreferenceKey

swift 复制代码
struct MyCustomPreferenceKey: PreferenceKey {
    // 默认值
    static var defaultValue: Int = 0
    
    // 合并策略 - 这里取最大值
    static func reduce(value: inout Int, nextValue: () -> Int) {
        value = max(value, nextValue())
    }
}

基本使用流程

  1. 定义 PreferenceKey
  2. 子视图设置偏好值 (使用 .preference(key:value:)
  3. 父视图监听变化 (使用 .onPreferenceChange(_:perform:)

实际应用示例

1. 传递简单值

swift 复制代码
struct MessagePreferenceKey: PreferenceKey {
    static var defaultValue: String = ""
    
    static func reduce(value: inout String, nextValue: () -> String) {
        value = nextValue()
    }
}

struct ChildView: View {
    var body: some View {
        Text("Hello")
            .preference(key: MessagePreferenceKey.self, value: "来自子视图的消息")
    }
}

struct ParentView: View {
    @State private var message = ""
    
    var body: some View {
        VStack {
            Text("收到消息: \(message)")
            ChildView()
        }
        .onPreferenceChange(MessagePreferenceKey.self) { value in
            message = value
        }
    }
}

2. 收集视图尺寸

swift 复制代码
struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

struct ContentView: View {
    @State private var childSize: CGSize = .zero
    
    var body: some View {
        VStack {
            Text("子视图尺寸: \(childSize.width) x \(childSize.height)")
            
            Rectangle()
                .fill(Color.blue)
                .frame(width: 200, height: 100)
                .background(
                    GeometryReader { geometry in
                        Color.clear
                            .preference(key: SizePreferenceKey.self, value: geometry.size)
                    }
                )
        }
        .onPreferenceChange(SizePreferenceKey.self) { size in
            childSize = size
        }
    }
}

3. 收集多个视图的位置信息

swift 复制代码
struct ViewFramePreferenceKey: PreferenceKey {
    static var defaultValue: [CGRect] = []
    
    static func reduce(value: inout [CGRect], nextValue: () -> [CGRect]) {
        value.append(contentsOf: nextValue())
    }
}

struct ContentView: View {
    @State private var frames: [CGRect] = []
    
    var body: some View {
        VStack {
            ForEach(0..<5) { index in
                Text("Item \(index)")
                    .padding()
                    .background(
                        GeometryReader { geometry in
                            Color.clear
                                .preference(
                                    key: ViewFramePreferenceKey.self,
                                    value: [geometry.frame(in: .global)]
                                )
                        }
                    )
            }
        }
        .onPreferenceChange(ViewFramePreferenceKey.self) { newFrames in
            frames = newFrames
        }
    }
}

高级用法

1. 自定义坐标空间

swift 复制代码
struct ContentView: View {
    @State private var headerFrame: CGRect = .zero
    
    var body: some View {
        VStack {
            Text("Header")
                .padding()
                .background(
                    GeometryReader { geometry in
                        Color.clear
                            .preference(
                                key: HeaderFramePreferenceKey.self,
                                value: geometry.frame(in: .named("container"))
                            )
                    }
                )
            
            Text("Header frame: \(headerFrame.debugDescription)")
        }
        .coordinateSpace(name: "container")
        .onPreferenceChange(HeaderFramePreferenceKey.self) { frame in
            headerFrame = frame
        }
    }
}

struct HeaderFramePreferenceKey: PreferenceKey {
    static var defaultValue: CGRect = .zero
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
        value = nextValue()
    }
}

2. 组合多个 PreferenceKey

swift 复制代码
struct CombinedPreferenceView: View {
    @State private var size: CGSize = .zero
    @State private color: Color = .black
    
    var body: some View {
        VStack {
            Text("Size: \(size.width)x\(size.height)")
            Text("Color: \(color.description)")
            
            ColorView()
        }
        .onPreferenceChange(SizePreferenceKey.self) { size = $0 }
        .onPreferenceChange(ColorPreferenceKey.self) { color = $0 }
    }
}

struct ColorView: View {
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .background(
                GeometryReader { geometry in
                    Color.clear
                        .preference(key: SizePreferenceKey.self, value: geometry.size)
                        .preference(key: ColorPreferenceKey.self, value: .blue)
                }
            )
    }
}

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

注意事项

  1. 性能优化:偏好值变化会触发视图更新,应避免频繁更新

  2. reduce 方法实现:根据需求合理实现合并策略(取最大值、累加、替换等)

  3. 与 GeometryReader 配合:常用于获取视图的几何信息

  4. 调试技巧 :可以使用 .transformPreference 修改偏好值

PreferenceKey 提供了一种强大的机制,使 SwiftUI 视图能够在不破坏声明式范式的情况下进行通信,是实现复杂布局和交互的重要工具。

相关推荐
我没想到原来他们都是一堆坏人几秒前
(未完待续...)如何编写一个用于构建python web项目镜像的dockerfile文件
java·前端·python
前端Hardy21 分钟前
HTML&CSS:有趣的漂流瓶
前端·javascript·css
前端Hardy23 分钟前
HTML&CSS :惊艳 UI 必备!卡片堆叠动画
前端·javascript·css
无羡仙1 小时前
替代 Object.freeze 的精准只读模式
前端·javascript
web前端1231 小时前
Java客户端开发指南 - 与Web开发对比分析
前端
龙在天1 小时前
前端 9大 设计模式
前端
搞个锤子哟1 小时前
网站页面放大缩小带来的问题
前端
hj5914_前端新手1 小时前
React 基础 - useState、useContext/createContext
前端·react.js
半花1 小时前
【Vue】defineProps、defineEmits 和 defineExpose
前端·vue.js
霍格沃兹_测试1 小时前
软件测试 | 测试开发 | H5页面多端兼容测试与监控
前端