SWIFTUI view not completely refreshed!

Hello fellow developers

here is something that I don t fully grasp :

1/ I have a fake SceneKit with two nodes both having light 2/ I have a small widget to explore those lights and tweak some param

-> in the small widget I can t update a toggle item when a new light is selected while other params are updated !

here is a short sample that illustrate what I am trying to resolve

import SwiftUI
import SceneKit
class ShortScene {
    var scene = SCNScene()
    var lightNodes          : [SCNNode] {
        get {scene.rootNode.childNodes(passingTest: { current, stop in current.light != nil} ) }
    }

    init() {
        let light1 = SCNLight()
        light1.castsShadow = false
        light1.type         = .omni
        light1.intensity    = 100
        
        let nodelight1 = SCNNode()
        nodelight1.light = light1
        nodelight1.name = "nodeLight1"
        
        scene.rootNode.addChildNode(nodelight1)
        
        let light2 = SCNLight()
        light2.castsShadow = false
        light2.type         = .ambient
        light2.intensity    = 300

        let nodelight2 = SCNNode()
        nodelight2.light = light2
        nodelight2.name = "nodeLight2"
        
        scene.rootNode.addChildNode(nodelight2)
    }

    
}



extension SCNLight : ObservableObject {}
extension SCNNode : ObservableObject {}

struct LightViewEx : View {
    @ObservedObject var lightParam : SCNLight
    @ObservedObject var lightNode : SCNNode
    var bindCol : Binding<Color>
    @State var castShadows : Bool
    
    init( _ _lightNode : SCNNode) {
        
        if let _light = _lightNode.light {
            lightParam = _light
            lightNode = _lightNode

            bindCol  = Binding<Color>( get: { if let _lightcol = _lightNode.light!.color as! NSColor? { return Color(_lightcol)} else { return Color.red } },
                                       set: { newCol in _lightNode.light!.color = NSColor(newCol) } )
        
            castShadows  = _lightNode.light!.castsShadow

           
            print( "For \(lightNode.name!) : CShadows \(castShadows)")

        } else {
            fatalError("No Light attached to Node")
        }
    }
    var body : some View {
       
        VStack(alignment: .leading) {
            Text("Light Params")
            Picker("Type",selection : $lightParam.type) {
                Text("IES").tag(SCNLight.LightType.IES)
                Text("Ambient").tag(SCNLight.LightType.ambient)
                Text("Directionnal").tag(SCNLight.LightType.directional)
                Text("Directionnal").tag(SCNLight.LightType.directional)
                Text("Omni").tag(SCNLight.LightType.omni)
                Text("Probe").tag(SCNLight.LightType.probe)
                Text("Spot").tag(SCNLight.LightType.spot)
                Text("Area").tag(SCNLight.LightType.area)
            }
            
            ColorPicker("Light Color", selection: bindCol)
            Text("Intensity")
            TextField("Intensity", value: $lightParam.intensity, formatter: NumberFormatter())
            
            Divider()

//            Toggle("shadows", isOn: $lightParam.castsShadow ).onChange(of: lightParam.castsShadow, { lightParam.castsShadow.toggle() })

            Toggle("CastShadows", isOn: $castShadows )
                .onChange(of: castShadows) { lightParam.castsShadow = castShadows;print("castsShadows changed to \(castShadows)") }
        }
    }
}

struct sceneView : View {
    @State var _lightIdx : Int = 0
    @State var shortScene = ShortScene()
    
    var body : some View {
        VStack(alignment: .leading) {
            if shortScene.lightNodes.isEmpty == false {
                Picker("Lights",
                       selection: $_lightIdx) {
                    ForEach(0..<shortScene.lightNodes.count, id: \.self) { index in
                        Text(shortScene.lightNodes[index].name ?? "NoName" ).tag(index)
                    }
                }
                
                GridRow(alignment: .top) {
                    LightViewEx(shortScene.lightNodes[_lightIdx])
                }
            }
        }
    }
}





struct testUIView: View {
    var body: some View {
        sceneView()
    }
}

#Preview {
    testUIView()
}

Something is obviously not right ! Anyone has some idea ?

Post not yet marked as solved Up vote post of Pom73 Down vote post of Pom73
288 views

Replies

Answer in this thread actually https://stackoverflow.com/questions/68042687/how-to-make-a-non-observableobject-observable

-> one user smartly proposed this @dynamicMemberLookup class Observable<M: AnyObject>: ObservableObject { var model: M init(_ model: M) { self.model = model }

subscript<T>(dynamicMember kp: WritableKeyPath<M, T>) -> T {
    get { model[keyPath: kp] }
    set {
        self.objectWillChange.send() // signal change on property update
        model[keyPath: kp] = newValue
    }
}

}

-> virtually turning any non observable class into one :-)