我对 ButtonStyle 的功能有问题
makeBody
会触发 PreferenceKey reduce
功能,如果我在 switch
内有 if
或 makeBody
语句。
我有以下代码:
struct TestPreferenceKey: PreferenceKey {
static var defaultValue: Int = 0
static func reduce(value: inout Int, nextValue: () -> Int) {
print("reduce value:", value)
print("reduce nextValue:", nextValue())
value = nextValue()
}
}
struct TestButtonStyle: ButtonStyle {
enum `Type` {
case a
case b
}
let type: `Type`
func makeBody(configuration: Configuration) -> some View {
switch type {
case .a:
configuration.label
case .b:
configuration.label
}
}
}
struct ButtonView: View {
var body: some View {
Button {
} label: {
Text("Button")
}
.buttonStyle(TestButtonStyle(type: .a))
}
}
struct ContentView: View {
var body: some View {
VStack {
ButtonView()
ButtonView()
}
.onPreferenceChange(TestPreferenceKey.self) { value in
print("value:", value)
}
}
}
我在日志中有下一个打印:
reduce value: 0
reduce nextValue: 0
value: 0
据我了解
PreferenceKey
的工作原理,我应该不会打印任何内容。
如果我从 ButtonStyle 的 makeBody
: 调用不同的函数,它就可以工作了
struct TestButtonStyle: ButtonStyle {
enum `Type` {
case a
case b
}
let type: `Type`
func makeBody(configuration: Configuration) -> some View {
switcher(configuration: configuration)
}
func switcher(configuration: Configuration) -> some View {
switch type {
case .a:
configuration.label
case .b:
configuration.label
}
}
}
现在,我什么也没打印出来。 但是,如果我将
@ViewBuilder
包装器添加到 switcher
函数,我会再次打印出来:
...
@ViewBuilder
func switcher(configuration: Configuration) -> some View {
switch type {
case .a:
configuration.label
case .b:
configuration.label
}
}
reduce value: 0
reduce nextValue: 0
value: 0
这是
ViewBuilder
的预期行为吗?或者这是一个错误?
总是需要用附加函数来进行这样的遍历吗?
如果有人知道答案,将不胜感激!我也将不胜感激有关该主题的有用链接!
UDP:
如果我为其中一个视图添加 .preference,我会得到 TestPreferenceKey 值的错误结果:
...
var body: some View {
VStack {
ButtonView()
.preference(
key: TestPreferenceKey.self,
value: 3
)
ButtonView()
}
.onPreferenceChange(TestPreferenceKey.self) { value in
print("value:", value)
}
}
结果是:
reduce value: 3
reduce nextValue: 0
value: 0
虽然 TestPreferenceKey 值应该是 3。 当然,没有
@ViewBuilder
上的 switcher
TestPreferenceKey 值是正确的 - 3
打印出来:
value: 3
reduce
的目的是让SwiftUI结合同级视图的偏好值。
此方法按视图树顺序接收其值。从概念上讲,这将一棵树的偏好值与其下一个兄弟树的偏好值结合起来。
没有记录是否
reduce
在任何特定情况下都会被调用,所以你不应该对此做出假设。如果 SwiftUI 觉得需要“将一棵树的偏好值与其下一个兄弟树的偏好值结合起来”,则可以调用 reduce
。
在您的示例中,调用
reduce
是因为 SwiftUI 尝试将第一个 ButtonView
的偏好值与第二个 ButtonView
的偏好值组合起来,为它们所在的 VStack
生成组合的偏好值。不过,SwiftUI 并不能“保证”做到这一点。正如您所经历的,删除 @ViewBuilder
会导致 reduce
不被调用。 SwiftUI“看到”第二个 ButtonView
没有 preference
修饰符,并且只采用第一个 ButtonView
的首选项值。这意味着您的 reduce
实现不应根据是否为没有首选项值的视图调用而产生不同的结果。即以下两个
VStack
应具有相同的组合偏好值:VStack {
Text("Foo")
.preference(
key: TestPreferenceKey.self,
value: 3
)
Text("Bar") // reduce is **not** called to combine this with 3
}
VStack {
Text("Foo")
.preference(
key: TestPreferenceKey.self,
value: 3
)
Text("Bar")
.preference(
key: TestPreferenceKey.self,
value: 0
) // reduce **is** called to combine this with 3
}
事实上,
的文档指出:没有显式键值的视图会生成此默认值。组合子视图可能会删除使用默认值生成的隐式值。这意味着
reduce(value: &x, nextValue: {defaultValue})
不应该改变
的含义。 对于您的x
reduce
实施来说,这显然是不正确的,因此您的实施是不正确的。
旁注:@ViewBuilder
在这里的不同之处在于它将
if
语句转换为 _ConditionalContent
视图。大概是因为一些实现细节,SwiftUI 对此的处理方式有所不同。如果这种行为将来发生变化,我不会感到惊讶。