SwiftUI ObservableObject 的不一致行为取决于调用站点的来源

Inconsistent behavior of SwiftUI ObservableObject depending on source of call site

在将数据从 SpriteKit 场景传递到 SwiftUI 视图的持续探索中,我发现了以下谜团(至少对我而言)。我希望解决方案可以打破僵局。 我有一个 ContentView,它使用 SpriteView() contain/display 一个名为 GameScene 的 SpriteKit 场景。 我有一个名为 Counter() 的 class,它被子class 编辑为一个 ObservableObject。 (注意 add(count) 函数主体中的打印语句。)

import SwiftUI

class Counter: ObservableObject {
@Published var count : Int = 0 

func add(count: Int) {
    self.count += count
    print("Add \(count); new total: \(self.count)")
 }
}

在 ContentView 中,为了测试和比较,我添加了一个调用 add(count) 函数的按钮:

import SwiftUI
import SpriteKit

struct ContentView: View {
    @ObservedObject var counter = Counter()
    var scene: SKScene {
        let scene = GameScene()
            scene.size = CGSize(width: 300, height: 400)
            scene.scaleMode = .fill
            return scene
        }  
    var body: some View {
        VStack{
            SpriteView(scene: scene)
                .frame(width: 300, height: 400)
                .ignoresSafeArea()
            
            Button{
                counter.add(count: 1)
            } label: {
                Text("Add to count")
            }
            Text("New count = \(counter.count)")
        }
    }
}

点击按钮(在 ContentView 中)时,计数会增加并按预期立即显示。

在 GameScene 中,我对 add(count) 函数进行了几乎相同的调用,但更新 ContentView 失败(拒绝?)。

class GameScene: SKScene {
var counter = Counter()
var count = 0
...
//a SpriteKitNode called "button" is created then added in didMove(toView)//
...
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let location = touch.location(in: self)
        if button.contains(location) {
            counter.add(count: 1)
        }
    }
 }

无论调用来自 GameScene 还是 ContentView,打印语句的内容都是一样的。第一次点击任一按钮时,它会显示:

加1;新总数:1

加1;新总数:2

加1;新总数: 3 ,依此类推。

换句话说,在调用旨在更新发布的 var 的 func 之前,它们似乎 表现相同。但是...

谜团:

为什么来自 ContentView 的调用触发了所需的更新,而来自 GameScene 的相同调用却没有?

我期待从疲倦的眼睛上取下鳞片!

在您的 GameScene 中,当您声明 属性:

时,您正在创建 Counter 的全新实例
var counter = Counter()

相反,您应该将 ContentView 拥有的 Counter 的实例传递给 GameScene,以便它们改变同一个对象。

您可以为 GameScene 创建一个初始化程序以将 Counter 作为参数,或者您可以这样做:

//in GameScene:
var counter : Counter?
//in GameScene when the button is pressed:
counter?.add(count: 1)

//in ContentView:
let scene = GameScene()
scene.counter = counter