Search code examples
swiftswiftuisprite-kitcombine

Swift value changes to static when entering SKScene


I have a SwiftUI View holding a SpriteView. This SpriteView receives a Double value successful from a combine variable sitting in a conductor class:

var passThrough = PassthroughSubject<Double, Never>()

This is the SwiftUI View which receives the PassthroughSubject correct.

struct SpriteKitView: View {
    
    @ObservedObject var sharedViewModel: SharedViewModel
    var scene = GameScene()
    
    init(sharedViewModel:SharedViewModel){
        let width = UIScreen.main.bounds.width
        let height = UIScreen.main.bounds.height
        scene.size = CGSize(width: width, height: height)
        scene.scaleMode = .fill
        self.sharedViewModel = sharedViewModel
    }
    
    var body: some View {
        
        SpriteView(scene: scene, options: [.allowsTransparency])
            .onReceive(sharedViewModel.conductor.passThrough){ ( value ) in
                
                print("Received Double: \(value)")
                
                scene.scaleVar = CGFloat(value)
            }
    }
}

The GameScene is just a circle drawn as a SKShapeNode. Within the Update you can see the passThrough enter like so:

class GameScene: SKScene {
        
    var scaleVar: Double = 0
    var myObject: SKShapeNode!
    
    override func didMove(to view: SKView) {
        let node = SKShapeNode(circleOfRadius: 100)
        node.position = CGPoint(x: size.width/2, y: size.height/2)
        node.fillColor = SKColor.orange
        myObject = node
        addChild(myObject)
    }
    
    override func update(_ currentTime: TimeInterval) {
        
        print("Update Double: \(scaleVar)")
        
        myObject.setScale(CGFloat(scaleVar))
        
        print("Update CGFloat: \(CGFloat(scaleVar))")
                
    }
}

Going from SwiftUI to the SpriteKit the value gets converted to a random Number. See this console output of the prints within the code:

Received Double: 0.9631603454475357 <- correct value

Update Double: 0.03565312499999999 <- random static??

Update CGFloat: 0.03565312499999999 <- random static??

Update Double: 0.03565312499999999 <- random static??

Update CGFloat: 0.03565312499999999 <- random static??

Why is the update not receiving the send Double. The same thing happens when I translate to CGFloat before sending it.


Solution

  • I took your code and put it into a new iOS app project replacing the default "ContentView" with your SpriteKitView and and the following implementation of a SharedViewModel. I could not reproduce the results you report. The double value is published as expected in every case. Is there perhaps another explanation in your code?

    
    import SwiftUI
    import SpriteKit
    import Combine
    
    
    class SharedViewModel: NSObject, ObservableObject {
        var passThrough = PassthroughSubject<Double, Never>()
    
        override init() {
            super.init()
            self.sendDoubles()
        }
    
        private func sendDoubles() {
            Task {
                var angle = 0.0
                while(true) {
                    guard !Task.isCancelled else { break }
                    angle = fmod(angle + 0.1, 2 * Double.pi)
                    passThrough.send(sin(angle))
                    try await Task.sleep(nanoseconds: UInt64(1.0e8))
                }
            }
        }
    }
    
    class GameScene: SKScene {
        var scaleVar: Double = 0
        var myObject: SKShapeNode!
    
        override func didMove(to view: SKView) {
            let node = SKShapeNode(circleOfRadius: 100)
            node.position = CGPoint(x: size.width/2, y: size.height/2)
            node.fillColor = SKColor.orange
            myObject = node
            addChild(myObject)
        }
    
        override func update(_ currentTime: TimeInterval) {
    
            print("Update Double: \(scaleVar)")
    
            myObject.setScale(CGFloat(scaleVar))
    
            print("Update CGFloat: \(CGFloat(scaleVar))")
    
        }
    }
    
    struct ContentView: View {
        @ObservedObject var sharedViewModel: SharedViewModel
        var scene = GameScene()
    
        init(sharedViewModel:SharedViewModel){
            let width = UIScreen.main.bounds.width
            let height = UIScreen.main.bounds.height
            scene.size = CGSize(width: width, height: height)
            scene.scaleMode = .fill
            self.sharedViewModel = sharedViewModel
        }
    
        var body: some View {
            SpriteView(scene: scene, options: [.allowsTransparency])
                .onReceive(sharedViewModel.passThrough.receive(on: DispatchQueue.main)){ ( value ) in
    
                    print("Received Double: \(value)")
    
                    scene.scaleVar = CGFloat(value)
                }
        }
    }