什么是 PreferenceKey
?
#
PreferenceKey
是 SwiftUI 中用于在视图树中向上传递数据的机制。SwiftUI 是单向数据流架构,数据通常是从上向下传递的(例如通过 @Binding
或 @State
),而 PreferenceKey
提供了一种从子视图向父视图传递数据的方式。
PreferenceKey 的作用 #
数据向上传递(向父视图传递数据)
- 在 SwiftUI 的视图层次结构中,
PreferenceKey
允许子视图将数据传递给它的祖先视图。 - 这对于不能直接使用绑定传递数据的场景非常有用。
- 在 SwiftUI 的视图层次结构中,
全局数据收集和整合
- 父视图可以使用
PreferenceKey
聚合子视图中的多个值,例如子视图的位置、尺寸,或其他自定义数据。
- 父视图可以使用
实现自定义布局和功能
- 优化视图的测量和布局。
- 在某些框架或系统控件外部,传递数据到父级实现动态行为。
PreferenceKey 的使用场景 #
1. 动态布局 #
- 计算和收集子视图信息以动态调整父视图布局。例如获取子视图的尺寸或位置来调整父布局。
2. 内容协调 #
- 父视图需要协调子视图的一些内容(如尺寸、偏移量等)以实现统一的视觉效果。
3. 状态广播 #
- 存储全局信息,比如从某些子视图中收集信息并传递到祖先父视图。例如向
ScrollView
的父视图发送滚动偏移量。
4. 工具栏整合 #
- 子视图向父视图传递内容,用于构建动态工具栏项或导航栏行为。
PreferenceKey 的核心组成部分 #
PreferenceKey
是一个协议,其核心是以下内容:
方法/属性 | 说明 |
---|---|
associatedtype | 定义需要通过 PreferenceKey 传递的数据的类型,一般需要遵守 Equatable 。 |
defaultValue | 提供一个默认值,当没有子视图设置数据时,父视图使用这个默认值。 |
reduce(value:nextValue:) | 自定义如何累积多个子视图设置的值并传递给父视图。 |
PreferenceKey 的基本用法 #
1. 定义 PreferenceKey 类型 #
创建一个结构体来实现 PreferenceKey
协议,定义数据和累积逻辑。
struct MyPreferenceKey: PreferenceKey {
static var defaultValue: String = ""
static func reduce(value: inout String, nextValue: () -> String) {
value += nextValue() // 累积传递数据
}
}
defaultValue
: 定义默认值为""
。reduce
: 定义聚合方式,在多子视图场景下合并数据。
2. 子视图设置偏好值 #
子视图使用 .preference(key:value:)
修饰符为 PreferenceKey
设置值。
struct ChildView: View {
var body: some View {
Text("Hello, SwiftUI!")
.preference(key: MyPreferenceKey.self, value: "SwiftUI rocks
") // 设置值
}
}
3. 父视图读取并响应偏好值 #
父视图通过 .onPreferenceChange
获取 PreferenceKey
的值。
struct ParentView: View {
@State private var value: String = ""
var body: some View {
VStack {
Text("PreferenceKey Value:")
Text(value)
.font(.headline)
ChildView()
}
.onPreferenceChange(MyPreferenceKey.self) { newValue in
value = newValue // 响应子视图的传值
}
}
}
完整示例:收集子视图宽度并动态调整父布局 #
示例:一个父视图动态捕获子视图的最大宽度,用于调整布局。 #
import SwiftUI
struct WidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue()) // 计算子视图中宽度的最大值
}
}
struct ChildView: View {
var text: String
var body: some View {
Text(text)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
.overlay(
GeometryReader { geometry in
Color.clear
.preference(key: WidthPreferenceKey.self, value: geometry.size.width)
} // 使用 GeometryReader 获取宽度
)
}
}
struct ParentView: View {
@State private var maxWidth: CGFloat = 0
var body: some View {
VStack {
HStack {
ChildView(text: "Short")
ChildView(text: "A bit longer text")
ChildView(text: "This is the longest text")
}
.frame(maxWidth: maxWidth) // 父视图动态设置最大宽度
Text("Maximum width: \(maxWidth)").padding()
}
.onPreferenceChange(WidthPreferenceKey.self) { newValue in
maxWidth = newValue // 更新最大宽度
}
.padding()
}
}
代码解析: #
定义
WidthPreferenceKey
- 默认值为
0
。 - 使用
reduce
合并多个子视图的宽度,取最大值。
- 默认值为
子视图传递宽度
- 使用
GeometryReader
动态获取子视图尺寸。 - 调用
.preference(key:value:)
将宽度传递给PreferenceKey
。
- 使用
父视图响应并更新
- 使用
.onPreferenceChange
获取计算出的最大宽度,动态调整布局。
- 使用
运行效果: #
- 子视图的宽度被父视图捕获,父视图的
HStack
宽度动态调整为子视图中最大宽度。
PreferenceKey 使用场景与案例 #
1. 动态布局调整 #
- 收集子视图的宽度、位置等信息,将这些信息传递给父视图以动态调整布局。
2. 实现导航栏或工具栏内容动态更新 #
- 子视图可以动态为工具栏设置内容,例如:
- 子视图可通过
PreferenceKey
的方式向父视图传递标题、按钮等工具栏信息。
- 子视图可通过
工具栏动态更新示例: #
struct ToolbarPreferenceKey: PreferenceKey {
static var defaultValue: String = "Default Title"
static func reduce(value: inout String, nextValue: () -> String) {
value = nextValue()
}
}
struct ContentView: View {
@State private var title: String = "Default Title"
var body: some View {
VStack {
Text("Hello, Toolbar!")
ChildView()
}
.onPreferenceChange(ToolbarPreferenceKey.self) { newValue in
title = newValue
}
.toolbar {
ToolbarItem(placement: .principal) {
Text(title) // 根据子视图动态更新标题
}
}
}
}
struct ChildView: View {
var body: some View {
Text("Dynamic Title Here")
.preference(key: ToolbarPreferenceKey.self, value: "Dynamic Title")
}
}
- 子视图改变工具栏标题,父视图自动同步更新。
PreferenceKey 的优缺点 #
优点: #
- 数据向上传递: 解决子视图无法直接向父视图传递数据的问题。
- 灵活合并逻辑: 通过
reduce
可以灵活定义合并规则(如取最大值、聚合子视图数据等)。 - 动态布局支持: 适用于动态调整父视图布局或全局状态。
缺点: #
- 可能更复杂: 对于简单的场景,PreferenceKey 相较于
@Environment
或@Binding
显得复杂。 - 数据传递范围有限: PreferenceKey 更适合在视图树中局部范围内传递数据。
总结 #
PreferenceKey
是 SwiftUI 里向上传递数据 的核心工具。- 适用场景:
- 动态布局调整(如子视图动态布局)。
- 数据聚合(如宽度、高度、工具栏内容等)。
- 核心操作:
- 定义
PreferenceKey
。 - 子视图通过
.preference(key:value)
提供值。 - 父视图通过
.onPreferenceChange
或GeometryReader
响应。
- 定义
通过熟练使用 PreferenceKey
,可以解决很多复杂的视图布局和交互需求。