UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

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      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

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.