为什么@State数组属性中的闭包不触发状态更改?

时间:2020-05-20 00:53:53

标签: swift swiftui

test视图具有@State showTitletitleitems,其中title值文本由分配给CTA {{ 1}}。

show title状态更改时,showTitle视图的正文内容中显示的值也相应更改:

test

尽管Text({ self.showTitle ? "Yes, showTitle!" : "No, showTitle!" }()) 是数组closure中的值的情况不变。为什么闭包不触发标题状态?

items

我已经用Foobar作为NestedView(title: $0.title()) Struct进行了测试。

Class

可以预期的是,“案例2”具有与“案例1”中类似的副作用,应该在import SwiftUI struct Foobar: Identifiable { var id: UUID = UUID() var title: () -> String init (title: @escaping () -> String) { self.title = title } } struct test: View { @State var showTitle: Bool = true @State var title: String @State var items: [Foobar] var body: some View { VStack { Group { Text("Case 1") Text({ self.showTitle ? "Yes, showTitle!" : "No, showTitle!" }()) } Group { Text("Case 2") ForEach (self.items, id: \.id) { NestedView(title: $0.title()) } } Button("show title") { print("show title cb") self.showTitle.toggle() } }.onAppear { let data = ["hello", "world", "test"] for title in data { self.items.append(Foobar(title: { self.showTitle ? title : "n/a" })) } } } } struct NestedView: View { var title: String var body: some View { Text("\(title)") } } 切换上显示“ n / a”。

输出:

enter image description here

2 个答案:

答案 0 :(得分:1)

据我了解,初始代码不起作用的原因与传递给Array并保存值副本的showTitle属性有关

您应该责怪闭包在onAppear时获取值。基本上由于这个原因,SwiftUI不会在showTitle值更改时刷新列表,因为SwiftUI可以使用Binding来知道何时重新呈现列表。

我可以提供两种替代解决方案,它们不需要另一个类来保存bool值。两种解决方案都涉及与SwiftUI进行通信,您需要showTitle绑定才能刷新标题。

  1. 不要对title使用闭包,将标题计算推迟到列表生成器:
struct Foobar: Identifiable {
    var id: UUID = UUID()
    var title: String

    init (title: String) {
        self.title = title
    }    
}

...

ForEach (self.items, id: \.id) {
    NestedView(title: self.showTitle ? $0.title : "n/a" )
}

...

.onAppear {
    let data = ["hello", "world", "test"]
    self.items = data.map { Foobar(title: $0) }
}
  1. 将标题闭包转换为(Binding<Bool>) -> String,从视图中注入$showTitle绑定:
struct Foobar: Identifiable {
    var id: UUID = UUID()
    var title: ((Binding<Bool>) -> String)
    
    init (title: @escaping (Binding<Bool>) -> String) {
        self.title = title
    }
}

...

ForEach (self.items, id: \.id) {
    // here we pass the $showTitle binding, thus SwiftUI knows to re-render
    // the view when the binding value is updated
    NestedView(title: $0.title(self.$showTitle))
}

...

.onAppear {
    let data = ["hello", "world", "test"]
    self.items = data.map { Foobar(title: { $0.wrappedValue ? title : "n/a" })) }
}

我个人会采用第一个解决方案,因为它可以更好地传达意图。

答案 1 :(得分:0)

据我了解,初始代码不起作用的原因与传递给Array并保存showTitle副本的value属性有关(创建了一个唯一的副本数据)。

我确实认为@State将使其可控和可变,而closure将捕获并存储引用(创建共享实例)。换句话说,要拥有reference而不是复制的value!如果不是这样,请随时纠正我,但是根据我的分析,这就是我的样子。

话虽如此,我仍然保留了最初的思考过程,我仍然想将closure传递给Array,并传播状态变化,从而导致副作用,相应于对其的引用!

因此,我使用了相同的模式,但没有依赖showTitle Bool的原始类型,而是创建了一个符合协议Class的{​​{1}}:因为ObservableObjectClasses

所以,让我们看一下它是如何工作的:

reference types

预期结果:

enter image description here