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 视图能够在不破坏声明式范式的情况下进行通信,是实现复杂布局和交互的重要工具。

相关推荐
福兮说4 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理5 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆6665 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
Dovis(誓平步青云)5 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan6 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_7 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒7 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技7 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu8 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub
默_笙8 小时前
🛴 从散件到整机:DeepAgents 与 Agent 身上预留的那些"插槽"(前置介绍)
前端·javascript