ButtonStyle makeBody ViewBuilder 触发 PreferenceKey reduce

问题描述 投票:0回答:1

我对 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
ios swiftui
1个回答
0
投票

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
}

事实上,

defaultValue

的文档指出:

没有显式键值的视图会生成此默认值。组合子视图可能会删除使用默认值生成的隐式值。这意味着
reduce(value: &x, nextValue: {defaultValue})

不应该改变

x
的含义。

对于您的
reduce

实施来说,这显然是不正确的,因此您的实施是不正确的。

旁注:

@ViewBuilder

在这里的不同之处在于它将

if
语句转换为
_ConditionalContent
视图。大概是因为一些实现细节,SwiftUI 对此的处理方式有所不同。如果这种行为将来发生变化,我不会感到惊讶。
    

© www.soinside.com 2019 - 2024. All rights reserved.