pinglock Asked: 2025-03-05 01:40:38 +0800 CST2025-03-05 01:40:38 +0800 CST 2025-03-05 01:40:38 +0800 CST 一些 SwiftUI 修改器如何修改更高级别的视图? 772 大多数 SwiftUI 修饰符会修改层次结构中位于其下方的视图,但有些修饰符会修改层次结构中位于其上方的视图。在下面的示例中.navigationTitle,、.toolbar和.presentationDetents正在修改它们所呈现的导航堆栈和工作表中的元素,但它们安装在文本视图上。 从概念上讲,这些修饰符在内部是如何工作的?此外,我该如何创建自己的修饰符? 我正在创建自己的导航堆栈(出于无关紧要的原因),并且有一种方法看起来很有希望,但我对此表示怀疑。我当前的方法是在环境对象上设置属性的视图修饰符,环境对象是自定义导航堆栈的视图模型。 swift 1 个回答 Voted Best Answer Sweeper 2025-03-05T02:08:41+08:002025-03-05T02:08:41+08:00 偏好值沿视图层次结构向上传递数据,类似于环境值沿视图层次结构向下传递数据。 可以使用 设置首选项值preference,或使用 修改首选项值。如果您想根据首选项值添加背景/覆盖,则可以使用或transformPreference读取这些值。onPreferenceChangebackgroundPreferenceValueoverlayPreferenceValue 这是一个简单的例子,说明可以从父视图读取偏好值: struct SomeKey: PreferenceKey { static let defaultValue: Int = 0 static func reduce(value: inout Int, nextValue: () -> Int) { value += nextValue() } } struct Parent<Content: View>: View { let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { content .overlayPreferenceValue(SomeKey.self) { x in Text("\(x)") } } } #Preview { Parent { VStack { Color.red .preference(key: SomeKey.self, value: 2) Color.green .preference(key: SomeKey.self, value: 3) } } } 请注意reduce首选项键中的方法。此方法用于组合同级视图的首选项值,因为即使父视图的子视图设置了不同的首选项值,父视图也必须接收一个值。在上面的示例中,叠加层显示“5”。 实现必须满足的一个重要不变量是defaultValue必须是的标识reduce——reduce(value: &x, nextValue: {defaultValue})不应改变x。 navigationTitle用于transformPreference设置一些代表工具栏配置的首选项值。您可以通过打印来了解 SwiftUI 的内部结构 print(type(of: Text("").navigationTitle(""))) 这将打印: ModifiedContent<ModifiedContent<Text, TransactionalPreferenceTransformModifier<NavigationTitleKey>>, _PreferenceTransformModifier<ToolbarKey>> _PreferenceTransformModifier是适用的视图修饰符transformPreference,在这种情况下,它正在转换一些称为的首选项键ToolbarKey。 toolbar并且presentationDetents其工作方式更加不透明,并且可能无法使用偏好值来实现。 还有ContainerValues。与将数据尽可能向上发送到视图层次结构的首选项值(类似于将环境值尽可能向下发送到视图层次结构的方式)不同,容器值仅向上发送到一个级别,并且父级可以读取每个单独的子视图的容器值。这类似于tag工作原理。 为了完整性,这里有一个演示: extension ContainerValues { @Entry var someValue = 0 } struct Parent<Content: View>: View { let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { VStack { ForEach(subviews: content) { subview in subview .overlay { Text("\(subview.containerValues.someValue)") } } } } } #Preview { Parent { Color.red .containerValue(\.someValue, 2) Color.green .containerValue(\.someValue, 3) } }
偏好值沿视图层次结构向上传递数据,类似于环境值沿视图层次结构向下传递数据。
可以使用 设置首选项值
preference
,或使用 修改首选项值。如果您想根据首选项值添加背景/覆盖,则可以使用或transformPreference
读取这些值。onPreferenceChange
backgroundPreferenceValue
overlayPreferenceValue
这是一个简单的例子,说明可以从父视图读取偏好值:
请注意
reduce
首选项键中的方法。此方法用于组合同级视图的首选项值,因为即使父视图的子视图设置了不同的首选项值,父视图也必须接收一个值。在上面的示例中,叠加层显示“5”。实现必须满足的一个重要不变量是
defaultValue
必须是的标识reduce
——reduce(value: &x, nextValue: {defaultValue})
不应改变x
。navigationTitle
用于transformPreference
设置一些代表工具栏配置的首选项值。您可以通过打印来了解 SwiftUI 的内部结构这将打印:
_PreferenceTransformModifier
是适用的视图修饰符transformPreference
,在这种情况下,它正在转换一些称为的首选项键ToolbarKey
。toolbar
并且presentationDetents
其工作方式更加不透明,并且可能无法使用偏好值来实现。还有
ContainerValues
。与将数据尽可能向上发送到视图层次结构的首选项值(类似于将环境值尽可能向下发送到视图层次结构的方式)不同,容器值仅向上发送到一个级别,并且父级可以读取每个单独的子视图的容器值。这类似于tag
工作原理。为了完整性,这里有一个演示: