swift @ObservableObject正在更新但未刷新View Body

hs1rzwqc  于 11个月前  发布在  Swift
关注(0)|答案(2)|浏览(73)

我试图将游戏数据从主视图传递到ForEach中的子单元格。这看起来是可行的,可观察对象正在更新,但视图本身似乎没有更新。下面是我们创建单元格的主GameView

struct GameView: View {
@State var money: Float! = 0.0
@ObservedObject var moneyMakers: MoneyMakerModel
@State var companyName: String! = "Alpaca Inc."
@State var gameMoney: Float = SavedGame.shared.money

    @Environment(\.defaultMinListRowHeight) var minRowHeight
    
    init() {
        self.moneyMakers = MoneyMakerModel.shared
        GameManager.GAME_VIEW = self
    }
    
    var body: some View {
        ScrollView {
            HStack {
                Text("$\(gameMoney)")
                    .frame(maxWidth: .infinity)
                Text("Alpaca Inc.")
                    .frame(maxWidth: .infinity)
                    .font(Font.headline)
                Image(systemName: "gearshape")
                    .frame(maxWidth: .infinity, alignment: .trailing)
                Spacer()
            }
            
            Spacer()
            
            VStack {
                ForEach(moneyMakers.moneyMakers) { moneyMaker in
                    MoneyMakerCell(moneyMaker: moneyMaker)
                        .frame(maxWidth: .infinity)
                        .padding([.top, .leading, .trailing])
                }
                .frame(minHeight: minRowHeight * CGFloat(STARTING_MONEY_MAKERS.count))
            }
        }
    }

}

我正在使用一个模型来传递一个Observable对象,如下所示

class MoneyMakerModel: ObservableObject {
@Published var moneyMakers: \[MoneyMaker\] = SavedGame.shared.moneyMakers

    public static let shared = MoneyMakerModel()
    private init(){}

}

在我的牢房里,我就像这样吃这个。如果我在下面的“@observedObject”处的代码中放置一个断点,我可以看到它在MoneyMakerModel中的数据更新时被调用,但仍然没有在MoneyMakerCellView的主体上运行。

import SwiftUI

struct MoneyMakerCell: View {
@State var name: String! = "Placeholder"
@State var manager: Bool! = false
@State var incomeManagerRate: Float! = 0.0

    @State var level: Int! = 0
    @State var updateLevel: Int! = 0
    @State var maxUpdateLevel: Int! = 0
    
    @ObservedObject var moneyMaker: MoneyMaker // <--- This runs on breakpoint but no update below
    
    var body: some View {
        ZStack {
            Color.yellow.opacity(0.4)
            VStack {
                Spacer()
                HStack {
                    Spacer()
                    Text(name)
                        .font(.headline)
                        .frame(maxWidth: .infinity, alignment: .leading)
                    Button("Upgrade") {
                        do {
                            try GameManager.upgradeMoneyMaker(moneyMaker: moneyMaker)
                        } catch (let error) {
                            print("ERROR: \(error)")
                            // TODO: SHOW ERROR MESSAGE
                        }
                    }
                    .frame(maxWidth: .infinity, alignment: .trailing)
                    Spacer()
                }
                
                Spacer()
                ProgressView("Upgrade \(self.moneyMaker.updateLevel)/\(moneyMaker.maxUpdateLevel)", value: (Float(self.moneyMaker.updateLevel)), total: 100)
                    .padding([.leading, .trailing])
                Spacer()
                
                if !manager {
                    HStack {
                        Spacer()
                        Text("$\(incomeManagerRate)")
                            .frame(maxWidth: .infinity, alignment: .leading)
                        Button("Hire Manager") {
                            do {
                                try GameManager.upgradeMoneyMaker(moneyMaker: moneyMaker)
                            } catch (let error) {
                                print("ERROR: \(error.localizedDescription)")
                            }
                        }
                        .frame(maxWidth: .infinity, alignment: .trailing)
                        Spacer()
                    }
                }
                
                Spacer()
            }
        }
    }

}

我真的很想得到一些帮助,因为过去两天我一直在为它疯狂。我想我误解了观测对象是如何工作的,我很想理解并克服这个障碍。
我尝试切换到StateObject和@State,但没有成功

1wnzp6jl

1wnzp6jl1#

我认为问题出在MoneyMakerCell上。我将提供一些基本的代码,希望你能解决你的问题。这就是我创建可观察对象的方式。

class MoneyMakerModel: ObservableObject {
    @Published var moneyMakers: [String] = ["A", "B", "C"] // Your custom Object
}

这是我初始化我的Observable Object并将moneyMakerModel作为环境对象传递到我的MoneyMakerCell的地方。

struct GameView: View {
    @StateObject var moneyMakerModel = MoneyMakerModel() //Init
    
    var body: some View {
        VStack {
            //Just to check if its getting updated
            ForEach(moneyMakerModel.moneyMakers, id: \.self) { moneyMaker in
                Text(moneyMaker)
                    .frame(maxWidth: .infinity)
                    .background(.green)
            }
            
            ForEach(moneyMakerModel.moneyMakers, id: \.self) { moneyMaker in
                MoneyMakerCell(moneyMaker: moneyMaker)
                    .environmentObject(moneyMakerModel) // <-- HERE
                    .frame(maxWidth: .infinity)
                    .frame(height: 50)
                    .background(.red)
            }
        }
    }
}

这就是我的MoneyMakerCell的样子。

struct MoneyMakerCell: View {
    @EnvironmentObject var moneyMakerModel: MoneyMakerModel
    
    @State var moneyMaker: String
    
    var body: some View {
        Text(moneyMaker)
            .onTapGesture {
                if let moneyMakerIndex = moneyMakerModel.moneyMakers.firstIndex(where: { $0 == moneyMaker }) {
                    moneyMaker = String(Int.random(in: 0 ... 100))
                    
                    $moneyMakerModel.moneyMakers[moneyMakerIndex].wrappedValue = moneyMaker
                }
            }
    }
}

这是一个简单的例子,在您使用自定义对象的情况下,仅使用字符串。复制并粘贴到Xcode中,看看它是如何工作的,希望这能帮助你解决你的问题:)

tkclm6bt

tkclm6bt2#

一般

您的解决方案有几个问题。但首先,我建议阅读和工作,通过介绍教程。具体了解@State、@ObservedObject@StateObject的区别和用例。另外,了解ObservableObject的已发布属性的关键约束是什么。

为什么你看不到变化

因此,代码中的主要问题是,您正在使用一个ObservableObject,其发布的属性具有引用语义。这意味着,ObservableObject仅在 * 引用值 * 将更改时发送更改事件-而不是 * 当该引用的任何属性将更改时 *。虽然有解决方法,但通常建议保持简单,并为您发布的属性专门使用具有 * 值语义 * 的类型(Swift结构,原始类型等)。
所以,上面的这就是为什么你在视图中没有收到更改通知的原因--没有。

绑定模型到视图

另一个问题是如何提供模型。基本上,当任何视图本身正在创建模型时,您应该使用@StateObject属性 Package 器来声明模型的生命周期绑定到 * 底层 * 视图(而不是SwiftUI视图!)的生命周期。注意,在这种情况下,视图 * 拥有 * 模型,没有人可以看到模型(但可能会看到副作用)。模型是视图的私有模型。
当视图模型在视图之外定义并且与视图具有不同的生存期时,您应该使用@ObservedObject属性 Package 器将模型绑定到视图。惯用的方法是让父视图在子视图中设置模型。在你的代码中,你使用视图的初始化器来设置模型。

什么是UIView?

在这里,我担心有一个误解什么是SwiftUI的视图初始化器,什么是SwiftUI视图实际上是所有关于。不是你想的那样。SwiftUI是一个声明式UI框架。将SwiftUI View视为一个 * 函数 *,您可以在其中创建和修改底层的实际视图(渲染像素)。因此,SwiftUI视图是组合这些创建和突变函数的一种方法。SwiftUI视图的初始化器可能会设置create/modifier函数的那些参数(实际上,SwiftUI实现稍微复杂一些,它使用的闭包实际上只会在底层视图的初始化过程中调用一次)。
在你的代码中意味着什么:每当你对你的视图进行更改时,你就创建了这个SwiftUI视图,模型将被设置-每次都是如此。实际上,您只需要在创建底层视图(使用相同的SwiftUI视图)时设置它 * 一次 *。所以,即使你的代码看起来没有什么害处,你也应该避免使用令人困惑的代码,而采用惯用的方法。
嗯...
这份声明

GameManager.GAME_VIEW = self

这绝对是一个信号,你需要了解SwiftUI视图实际上是什么。我敢打赌,你试图完成的事情至少是非常麻烦的,可能不会起作用,或者有你还没有意识到的不良副作用。
”””好吧,接下来该怎么办?**
我建议从重构你的“应用状态”开始。请注意,AppState不是一个“Model”,它只是没有逻辑的普通数据。您的Model将添加逻辑并可能在内部修改应用状态。可以肯定的是,应该只有一个模型可以更改您的应用程序状态。您的逻辑将由模型上调用的事件和方法引起。然后模型发布新的已发布值。
通过使用具有值语义的类型(结构体、枚举)来定义您的应用程序状态。只是不要为App State使用类。
另外,不要在可变状态中使用共享全局变量!切勿腹主动脉瘤腔内修复术!
接下来,使用惯用的方法来定义SwiftUI视图,使用@StateObject@ObservableObject,其中绑定使用值语义发布值的模型。
不要使用SwiftUI Environment来传递视图状态,而是在SwiftUI View层次结构的某个根视图中,提取已发布的属性并将其传递给子视图。这样,子视图就不需要从模型中知道任何东西--它们只需要得到字符串、数字等。他们所呈现的。
我建议将环境用于依赖项和配置,而不是用于传递模型对象。这是可能的,但从设计的Angular 来看,它会导致不希望的耦合。
我想,这是很多事情。现在,轮到你了,编码快乐!

相关问题