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

Swift 为什么@State数组属性中的闭包不会触发状态更改?,swift,swiftui,Swift,Swiftui,测试视图具有@State showTitle、title和项目,其中标题值文本由分配给CTA show title的闭包控制 当showTitle状态更改时,测试视图主体内容中显示的值也会相应更改: Text({ self.showTitle ? "Yes, showTitle!" : "No, showTitle!" }()) 而闭包是数组项中的值的情况不变。为什么闭包没有触发标题状态 NestedView(title: $0.title()) 我已经用Foobar作为Struct和Cla

测试视图具有@State showTitle、title和项目,其中标题值文本由分配给CTA show title的闭包控制

当showTitle状态更改时,测试视图主体内容中显示的值也会相应更改:

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

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

我们期望的是,案例2会产生与案例1类似的副作用,在showTitle切换上显示n/a

输出:


据我所知,初始代码不起作用的原因与传递给数组并保存值副本的showTitle属性有关,它创建了数据的唯一副本

我确实认为@State会使它变得可控和可变,闭包会捕获并存储引用并创建一个共享实例。换句话说,有一个引用,而不是复制的值!如果不是这样的话,请随时纠正我,但根据我的分析,情况就是这样

话虽如此,我保留了最初的思考过程,我仍然希望向数组传递一个闭包,并传播状态更改,从而对它的任何引用产生副作用

因此,我使用了相同的模式,但不是依赖于showttitle Bool的基元类型,而是创建了一个符合协议observeObject的类:因为类是引用类型

那么,让我们来看看这是如何实现的:

import SwiftUI

class MyOption: ObservableObject {
    @Published var option: Bool = false        
}

struct Foobar: Identifiable {
    var id: UUID = UUID()
    var title: () -> String

    init (title: @escaping () -> String) {
        self.title = title
    }
}

struct test: View {
    @EnvironmentObject var showTitle: MyOption
    @State var title: String
    @State var items: [Foobar]

    var body: some View {
        VStack {
            Group {
                Text("Case 1")
                Text(self.showTitle.option ? "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.option.toggle()
                print("self.showTitle.option: ", self.showTitle.option)
            }
        }.onAppear {
            let data = ["hello", "world", "test"]
            for title in data {
                self.items.append(Foobar(title: { self.showTitle.option ? title : "n/a" }))
            }
        }
    }
}

struct NestedView: View {
    var title: String
    var body: some View {
        Text("\(title)")
    }
}
结果如预期:


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

你是对的,你责怪关闭在出现的时候捕获了价值。基本上正因为如此,当showTitle值更改时,SwiftUI不知道如何刷新列表,因为SwiftUI不需要使用绑定来知道何时重新呈现列表

我可以提供两种可选的解决方案,不需要另一个类来保存bool值。这两种解决方案都涉及到与SwiftUI的通信,即您需要showTitle绑定来刷新标题

不要对标题使用闭包,请将标题计算推迟到列表生成器: 结构Foobar:可识别{ 变量id:UUID=UUID 变量标题:字符串 初始化标题:字符串{ self.title=标题 } } ... ForEach self.items,id:\.id{ NestedViewtitle:self.showTitle?$0.title:n/a } ... 奥纳佩尔先生{ 让数据=[你好,世界,测试] self.items=data.map{Foobartitle:$0} } 将标题闭包转换为绑定->字符串闭包,从视图中插入$showTitle绑定: 结构Foobar:可识别{ 变量id:UUID=UUID 变量标题:绑定->字符串 初始化标题:@转义绑定->字符串{ self.title=标题 } } ... ForEach self.items,id:\.id{ //这里我们传递$showthile绑定,因此SwiftUI知道如何重新渲染 //更新绑定值时的视图 NestedViewtitle:$0.titleself.$showTitle } ... 奥纳佩尔先生{ 让数据=[你好,世界,测试] self.items=data.map{foobartile:{$0.wrappedValue?title:n/a} }
就我个人而言,我会选择第一种解决方案,因为它能更好地传达意图。

我看不出你认为你在哪里改变一个@State变量,它是一个闭包。你能更明确地说明你认为哪一行会这样做,以及你期望发生什么吗?嗨@matt,是的,这是NestedViewtitle:$0.title,标题调用,self.items.appendFoobartitle:{self.showtTitle?标题:n/a}我不明白。您是否认为更改您的州标题会在某种程度上左右摇摆,并更改Foobars中已经存在的标题?不是,而是在showTitle.toggle中,看到案例1中类似的副作用;这是为了将案例2OK的标题文本更改为n/a,这很清楚。好吧,所以你认为改变你的州剧名会在某种程度上改变现有的Foobar。那是不会发生的;Foobar的选择已经做出。如果你想改变你拥有的Foobar,你会说某事=self.showTitle?Foobartile:标题:Foobartile:不适用。
import SwiftUI

class MyOption: ObservableObject {
    @Published var option: Bool = false        
}

struct Foobar: Identifiable {
    var id: UUID = UUID()
    var title: () -> String

    init (title: @escaping () -> String) {
        self.title = title
    }
}

struct test: View {
    @EnvironmentObject var showTitle: MyOption
    @State var title: String
    @State var items: [Foobar]

    var body: some View {
        VStack {
            Group {
                Text("Case 1")
                Text(self.showTitle.option ? "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.option.toggle()
                print("self.showTitle.option: ", self.showTitle.option)
            }
        }.onAppear {
            let data = ["hello", "world", "test"]
            for title in data {
                self.items.append(Foobar(title: { self.showTitle.option ? title : "n/a" }))
            }
        }
    }
}

struct NestedView: View {
    var title: String
    var body: some View {
        Text("\(title)")
    }
}