BLACK FRIDAY: Save 50% on all my Swift books and bundles! >>

Selecting and Propagating SKNode to SwiftUI View

Forums > SwiftUI

Hi,

Below I've included the working, bare-bones app that displays two SpriteKit SKNodes in a view on one side of the screen, and (what should be) the selected node id in a view on the other side of the screen. However, the information is not propagating from one side to the other. I'm sure I'm missing something fundamental, so if anyone could assist I'd be very grateful.

AppCode:

import SwiftUI

@main
struct TestViewPropagationApp: App {

    @ObservedObject var vm = ProgramManagerViewModel()
    var body: some Scene {
        WindowGroup {
            ContentView(vm: vm)
        }
    }
}

Main Code:

import SwiftUI
import SpriteKit

protocol ObjectProtocol {
    var id : UUID { get set }
    var instanceId : Int { get }
}

class Object: ObjectProtocol {
    var id = UUID()
    var instanceId : Int

    init(instanceId: Int){
        self.instanceId = instanceId
    }
}

class ObjectNode<T: ObjectProtocol>: SKNode {
    var id : UUID
    var object: T

    init(object: T){
        self.id = object.id
        self.object = object
        super.init()

        let shape = SKShapeNode(rectOf: CGSize(width: 64, height: 64))
        shape.strokeColor = .red

        let label = SKLabelNode(text: String(object.instanceId))
        addChild(shape)
        addChild(label)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var isUserInteractionEnabled: Bool {
        set {
            // ignore
        }
        get {
            return true
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        let vm = ProgramManagerViewModel.shared

        print("Object Id \(object.id)")

        vm.pm.currentId = object.id

        print("VM Object Id \(String(describing: vm.pm.currentId))")

    }
}

class ProgramManager {
    static let shared = ProgramManager()
    var currentId : UUID?
    var objects = [Object]()
    let scene = NodeViewScene(size: CGSize(width: UIScreen.main.bounds.width * 0.5, height: UIScreen.main.bounds.height * 0.5))

    init(){

        scene.scaleMode = .resizeFill
        scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)

        var object = Object(instanceId: 1)
        var objectNode = ObjectNode(object: object)
        objectNode.position = CGPoint(x: 0, y: 0)

        objects.append(object)
        scene.addChild(objectNode)

        object = Object(instanceId: 2)
        objectNode = ObjectNode(object: object)
        objectNode.position = CGPoint(x: 128, y: 0)

        objects.append(object)
        scene.addChild(objectNode)
    }
}

class ProgramManagerViewModel : ObservableObject {
    static let shared = ProgramManagerViewModel()
    @Published var pm = ProgramManager.shared
}

class NodeViewScene: SKScene, ObservableObject {

    override init(size: CGSize){
        super.init(size: size)

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

struct LeftPanel: View {
    @ObservedObject var vm : ProgramManagerViewModel
    var body: some View {
        VStack {
            Text("Left Panel")
            GeometryReader { geo in
                SpriteView(scene: vm.pm.scene)
                    .frame(width: geo.size.width, height: geo.size.height, alignment: .center)
            }
        }
    }
}

struct RightPanel: View {
    @ObservedObject var vm : ProgramManagerViewModel

    var body: some View {
        VStack{
            Text("Right Panel")
            VStack {
                //  This never gets updated
                Text(String(vm.pm.currentId?.uuidString ?? "None Selected"))
                Button("Print Current Id"){
                    //  This Shows the correct value in the console window
                    let _ = print("Right Panel Current Id \(String(describing: vm.pm.currentId))")
                }
            }
            .frame(maxHeight: .infinity)
        }
        .frame(maxWidth: .infinity)

    }
}

struct ContentView: View {
    @ObservedObject var vm : ProgramManagerViewModel

    var body: some View {
        HStack {
            LeftPanel(vm: vm)
            RightPanel(vm: vm)
        }
        .padding()
    }
}

2      

Hi All,

Ok, I've solved it...or, at least, I've got it working (and I'm aware those are possibly two different things). Below is the working code, but if there is a preferred way of resolving it I'd be glad to hear it.

App Code:

import SwiftUI

let gvm = ProgramManagerViewModel.shared

@main

struct TestViewPropagationApp: App {

    @ObservedObject var vm = ProgramManagerViewModel.shared
    var body: some Scene {
        WindowGroup {
            ContentView(vm: vm)
        }
    }
}

Main Code:

import SwiftUI
import SpriteKit

protocol ObjectProtocol {
    var id : UUID { get set }
    var instanceId : Int { get }
}

class Object: ObjectProtocol {
    var id = UUID()
    var instanceId : Int

    init(instanceId: Int){
        self.instanceId = instanceId
    }
}

class ObjectNode<T: ObjectProtocol>: SKNode {
    var id : UUID
    var object: T

    init(object: T){
        self.id = object.id
        self.object = object
        super.init()

        let shape = SKShapeNode(rectOf: CGSize(width: 64, height: 64))
        shape.strokeColor = .red

        let label = SKLabelNode(text: String(object.instanceId))
        addChild(shape)
        addChild(label)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var isUserInteractionEnabled: Bool {
        set {
            // ignore
        }
        get {
            return true
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        print("Object Id \(object.id)")

        gvm.currentId = object.id

        print("VM Object Id \(String(describing: gvm.pm.currentId))")

    }
}

class ProgramManager  {
    static let shared = ProgramManager()
    var currentId : UUID?
    var objects = [Object]()
    let scene = NodeViewScene(size: CGSize(width: UIScreen.main.bounds.width * 0.5, height: UIScreen.main.bounds.height * 0.5))

    init(){

        scene.scaleMode = .resizeFill
        scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)

        var object = Object(instanceId: 1)
        var objectNode = ObjectNode(object: object)
        objectNode.position = CGPoint(x: 0, y: 0)

        objects.append(object)
        scene.addChild(objectNode)

        object = Object(instanceId: 2)
        objectNode = ObjectNode(object: object)
        objectNode.position = CGPoint(x: 128, y: 0)

        objects.append(object)
        scene.addChild(objectNode)
    }
}

class ProgramManagerViewModel : ObservableObject {
    static let shared = ProgramManagerViewModel()
    @Published var pm = ProgramManager.shared

    @Published var currentId : UUID? { didSet
        {
            pm.currentId = currentId
        }
    }
}

class NodeViewScene: SKScene, ObservableObject {

    override init(size: CGSize){
        super.init(size: size)

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

struct LeftPanel: View {
    @ObservedObject var vm : ProgramManagerViewModel
    var body: some View {
        VStack {
            Text("Left Panel")
            GeometryReader { geo in
                SpriteView(scene: vm.pm.scene)
                    .frame(width: geo.size.width, height: geo.size.height, alignment: .center)
            }
        }
    }
}

struct RightPanel: View {
    @ObservedObject var vm : ProgramManagerViewModel

    var body: some View {
        VStack{
            Text("Right Panel")
            VStack {
                //  This never gets updated
                Text(String(vm.pm.currentId?.uuidString ?? "None Selected"))
                Button("Print Current Id"){
                    //  This Shows the correct value in the console window
                    let _ = print("Right Panel Current Id \(String(describing: vm.pm.currentId))")
                }
            }
            .frame(maxHeight: .infinity)
        }
        .frame(maxWidth: .infinity)

    }
}

struct ContentView: View {
    @ObservedObject var vm : ProgramManagerViewModel

    var body: some View {
        HStack {
            LeftPanel(vm: vm)
            RightPanel(vm: vm)
        }
        .padding()
    }
}

2      

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.