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

相关推荐
不爱吃饭爱吃菜31 分钟前
uniapp微信小程序-长按按钮百度语音识别回显文字
前端·javascript·vue.js·百度·微信小程序·uni-app·语音识别
程序员拂雨1 小时前
Angular 知识框架
前端·javascript·angular.js
GoodStudyAndDayDayUp2 小时前
gitlab+portainer 实现Ruoyi Vue前端CI/CD
前端·vue.js·gitlab
程序员阿明2 小时前
vite运行只能访问localhost解决办法
前端·vue
前端 贾公子2 小时前
uniapp -- 验证码倒计时按钮组件
前端·vue.js·uni-app
淡笑沐白2 小时前
AJAX技术全解析:从基础到最佳实践
前端·ajax
龙正哲2 小时前
如何在Firefox火狐浏览器里-安装梦精灵AI提示词管理工具
前端·firefox
徐徐同学3 小时前
轻量级Web画板Paint Board如何本地部署与随时随地在线绘画分享
前端
LuckyLay3 小时前
Vue百日学习计划Day4-8——Gemini版
前端·vue.js·学习
八戒社3 小时前
WooCommerce短代码Shortcodes使用方法
前端·wordpress·woocommerce