0

In an ongoing quest to pass data from a SpriteKit scene to a SwiftUI view, I have discovered the following mystery (to me, at least). I hope the solution might break the impasse. I have a ContentView which uses SpriteView() to contain/display a SpriteKit scene called GameScene. I have a class called Counter(), which is subclassed as an ObservableObject. (Note the print statement in the body of the add(count) func.)

import SwiftUI

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

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

In ContentView, for the purpose of testing and comparison, I have added a button which calls the add(count) func:

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)")
        }
    }
}

When the button (in ContentView) is tapped, the count increments and is displayed immediately as expected.

In GameScene I have virtually the same call to the add(count) func, but it fails (refuses?) to update the 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)
        }
    }
 }

The print statement reads the same whether the call comes from GameScene or ContentView. With the first tap of either button it reads:

Add 1; new total: 1

Add 1; new total: 2

Add 1; new total: 3 , and so on.

In other words, up until the call to the func that is meant to update the published var, they seem to behave identically. But...

The Mystery:

Why does the call from ContentView trigger the desired update while the same call from GameScene does not?

I look forward to having the scales removed from my weary eyes!

1 Answer 1

2

In your GameScene, you're creating a brand new instance of Counter when you declare the property:

var counter = Counter()

Instead, you should be passing the instance of Counter owned by ContentView to GameScene so that they are mutating the same object.

You could create an initializer for GameScene to take a Counter as a parameter, or you could do something like this:

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

//in ContentView:
let scene = GameScene()
scene.counter = counter
Sign up to request clarification or add additional context in comments.

1 Comment

I followed the suggestion of @jnpdx which also required changing the call in GameScene to counter?.add(count: 1), and now it works. I suspected the problem was with the recreation of the instance and had tried using var counter: Counter? But I was missing the change to scene in ContentView. Much appreciation for taking the time to help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.