在 init() 构造函数中初始化的带有 @State 的 SwiftUI 视图

Posted

技术标签:

【中文标题】在 init() 构造函数中初始化的带有 @State 的 SwiftUI 视图【英文标题】:SwiftUI View with @State that is initialized in init() constructor 【发布时间】:2020-02-26 10:50:48 【问题描述】:

我有这样一个简单的复选标记 SwiftUI 视图。 它显示在列表中,可以通过点击它来切换,或者可以在从数据源(例如核心数据)刷新时切换

我的第一个实现是

struct RoundedCheckmark: View 

    @State var isChecked : Bool
    let onCheck: (Bool) -> Void

    func toggle()  isChecked = !isChecked; onCheck(isChecked) 

    init(isChecked: Bool, onCheck: @escaping (Bool) -> Void =  _ in ) 
        self._isChecked = State(initialValue: isChecked)
        self.onCheck = onCheck
    

    var body: some View 

        Button(action: toggle) 

            Image(isChecked ? "CheckedCheckmark" : "UncheckedCheckmark")
        
    

它似乎工作我可以切换它并正确加载。在切换时,我通过 onCheck 关闭/回调保存了更改,并且刷新正常。

但是在外部刷新(如推送通知刷新底层模型)之后,此视图的核心数据刷新不会正确更改 @State 属性 isChecked。

在 init() 中,我得到了 isChecked 的新值,并使用 State(initialValue: ) 重新初始化了 @State。但是在正文中,我从旧的 @State 属性中获取了 isChecked 的旧值。所以视图确实有错误的图像。

这是一种解决方法,即仅依赖 let isChecked 属性和 onCheck 回调。但是现在我的视图中不能有状态,我只能依赖外部数据源的变化。也许它更合适,因为这样我有一个没有本地 @State 存储的单一事实来源。

struct RoundedCheckmark: View 

    let isChecked: Bool
    let onCheck: (Bool) -> Void

    func toggle()  onCheck(isChecked) 

    init(isChecked: Bool, onCheck: @escaping (Bool) -> Void =  _ in ) 

        self.isChecked = isChecked
        self.onCheck = onCheck
    

    var body: some View 

        Button(action: toggle) 

            Image(isChecked ? "CheckedCheckmark" : "UncheckedCheckmark")

        

    

【问题讨论】:

看起来您回答了自己的问题... 是的,但我不明白为什么第一个解决方案不起作用。是因为 View 的每个实例都创建了新的@State,但 View 使用的是旧的?如果是这样,那么我如何改变这种状态(以及我是否应该尝试改变它?)你的意思是为什么我会有两个真相来源? 【参考方案1】:

isChecked 作为@State 变量你的真实来源。这意味着当某些底层数据模型(如 CoreData)发生变化时,这无关紧要。您的视图仅查看isChecked 的本地@State 版本。

但是看看你的观点。对我来说,它不应该拥有自己的状态。为什么?因为这个视图作为复选标记视图没有语义意义。它的父级似乎拥有该状态(因此有一个onCheck 回调)。相反,这应该使用带有 no 回调的@Binding

struct RoundedCheckmark: View 
    @Binding var isChecked: Bool

    var body: some View 
        Button(action:  isChecked.toggle() ) 
            Image(isChecked ? "CheckedCheckmark" : "UncheckedCheckmark")
        
    

现在你的父母拥有这个状态,你可以推断它的语义:

struct CheckmarkOwner: View 
    @State var showFavoritesOnly = false

    var body: some View 
        // content

        RoundedCheckmark(isChecked: $showFavoritesOnly)

        // and now something else will get notified when `showFavoritesOnly` gets toggled
        if showFavoritesOnly 
             // toggleable content
        

        // more content
    

【讨论】:

我已经对其进行了测试,并且在其他地方它可以工作,即如果我将一些外部日期传递给 init() 而不是使用它们来初始化 @State 和 State(initialValue: )。然后,如果该数据在外部源中刷新,则再次调用 init(),并重新初始化 State(initialValue:),它通常可以正常工作。也有这种外部数据是 Core Data 托管对象的情况,那么重要的是要知道这个对象就像暂存器,有时我们可以更改它并认为刷新后它没有正确更新但它的状态没有滚动返回使用 ManagedObjectContext.rollBack

以上是关于在 init() 构造函数中初始化的带有 @State 的 SwiftUI 视图的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin类的初始化 ③ ( init 初始化块 | 初始化顺序 : 主构造函数属性赋值 -> 类属性赋值 -> init 初始化块代码 -> 次构造函数代码 )

Kotlin init 小记

Kotlin init 小记

python 构造器

C ++:如何在派生类中定义基类构造函数,如果基构造函数具有带有私有成员的初始化列表[重复]

Swift 构造函数