SwiftUI

SwiftUI-Preference

2025-06-08  本文已影响0人  YungFan

在 SwiftUI 中,组件间的数据传递通常依赖于如 @State、@Binding、@Environment 等机制。但如果希望将子视图中的某些状态或信息传递给父视图,该如何处理呢?答案就是:使用Preference

引入

import SwiftUI

// 模拟实现
// 1.遵守协议
struct NavigationBarTitleKey: PreferenceKey {
    static var defaultValue: String = ""
    
    static func reduce(value: inout String, nextValue: () -> String) {
        value = nextValue()
    }
}

struct ContentView: View {    
    var title: String = "标题"
    
    var body: some View {
        NavigationView {
            Text("SwiftUI")
                .navigationBarTitle(title)
                // 2.使用preference修饰符将(NavigationBarTitleKey,title)传出去
                .preference(key: NavigationBarTitleKey.self, value: title)
            }
            // 3.使用onPreferenceChange修饰符来观察NavigationBarTitleKey
            .onPreferenceChange(NavigationBarTitleKey.self) { title in
                // 打印Title
                print(title)
        }
    }
}
import SwiftUI

struct ContentView: View {
    var body: some View {      
        NavigationView { // 父View
            Text("SwiftUI") // 子View
                .navigationBarTitle("标题")
        }  
    }
}

PreferenceKey协议

public protocol PreferenceKey {    
    associatedtype Value

    static var defaultValue: Self.Value { get }

    static func reduce(value: inout Self.Value, nextValue: () -> Self.Value)
}

参数说明

使用步骤

  1. 定义 Preference 的类型,即 Value 的类型。
  2. 定义 defaultValue,如果在 View 的层次结构中从未设置过值,则使用该值。
  3. 实现 reduce 函数,合并在视图层次中不同级别设置的 Preference 值。

案例

import SwiftUI

struct SizePreferenceKey: PreferenceKey {    
    typealias Value = CGSize
    
    static let defaultValue: CGSize = .zero
    
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

// 计算某个View的Size
struct ContentView: View {
    @State private var textSize: CGSize = .zero
    
    var body: some View {
        VStack {
            Text("SwiftUI 实用教程")
                .background(
                    GeometryReader { proxy in
                        Color
                            .clear
                            // 通过preference将(Key,Value)传出去
                            .preference(key: SizePreferenceKey.self, value: proxy.size)
                    }
            )
            
            Text("\(String(describing: textSize))")
            
        }
            // 通过onPreferenceChange监听preference的变化,通过Key拿到Value
            .onPreferenceChange(SizePreferenceKey.self) { size in
                self.textSize = size
        }
    }
}

应用

Preference 是一个非常强大的工具,但它们主要用于通用视图组件(如NavigationView),这些组件可以包含非常复杂的视图层次结构。

struct PresentableAlert: Equatable, Identifiable {
    let id = UUID()
    let title: String
    let message: String
}
struct AlertPreferenceKey: PreferenceKey {
    static var defaultValue: PresentableAlert?

    static func reduce(value: inout PresentableAlert?, nextValue: () -> PresentableAlert?) {
        value = nextValue()
    }
}
import SwiftUI

struct ChildView: View {
    @State private var alert: PresentableAlert?
    
    var body: some View {
        ZStack {
            Color.orange
            VStack {
                Button("显示对话框", action: {
                    self.alert = PresentableAlert(title: "标题", message: "温馨提示") })
                    // 传递值
                    .preference(key: AlertPreferenceKey.self, value: alert)
            }
        }
    }
}
import SwiftUI

struct ContentView: View {
    @State private var alert: PresentableAlert?
    
    var body: some View {
        ChildView()
            // 获取值并保存到alert中
            .onPreferenceChange(AlertPreferenceKey.self) { self.alert = $0 }
            .alert(item: $alert) { alert in
                Alert(title: Text(alert.title), message: Text(alert.message))
        }
    }
}

总结

Preference 是 SwiftUI 中一个极其强大但也容易被忽视的功能,虽然用法稍显繁琐,但它能大幅拓展 SwiftUI 的表达力,尤其是在自定义复杂组件时。它适用于以下场景:

上一篇 下一篇

猜你喜欢

热点阅读