SceneKit change `.allowCameraControl` without refreshing

Viewed 69

I'm making a test program that allows the user to switch .allowCameraControl on and off with a button So I'm using observable objects that update the scene object whenever a variable changes. But every time the .allowCameraControl option is changed, the scene refreshes and objects goes back to its original orientation. How do I make it so that the object stays in its current orientation even when the .allowCameraControl changes

Here's a minimum reproducible example ContentView.swift:

import SwiftUI
import SceneKit

final class Lmao: ObservableObject {
    @Published var yo: SceneView.Options = [.allowsCameraControl]
}

struct ContentView: View {
    @EnvironmentObject var l: Lmao
    var scene = SCNScene(named: "myScene.scn")
    var body: some View {
        VStack {
            SceneView(scene: scene, options: l.yo)
                .frame(width: UIScreen.main.bounds.width , 
                      height: UIScreen.main.bounds.height / 2)
            
            Button("allow/disable camera controll") {
                if l.yo == [] {
                    l.yo = [SceneView.Options.allowsCameraControl]
                }
                else {
                    l.yo = []
                }
            }
        }
    }
    func updateLocation(_ location: CGPoint) {
        print(location)
    }
}

CameraTestApp.swift:

@main
struct CameraTestApp: App {
    @StateObject private var ll = Lmao()
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(ll)
        }
    }
}

myScene.scn: put some random stuff in a scene file

1 Answers

Common approach

In ContentView use @State property wrapper allowOrNot for toggling states:

import SwiftUI
import SceneKit

struct ContentView : View {
    
    @State private var text: String = "CamControl is On"
    @State private var allowOrNot: Bool = true
    
    var body: some View {
        ZStack {
            VRViewContainer(allowOrNot: $allowOrNot)
                .ignoresSafeArea()
            VStack {
                Text(text)
                    .onTapGesture {
                        allowOrNot.toggle()                            
                        if !allowOrNot { text = "CamControl is OFF" } 
                        else { text = "CamControl is On" }
                    }
                Spacer()
            }
        }
    }
}

In VRViewContainer use @Binding property wrapper.

struct VRViewContainer: UIViewRepresentable {
    
    @Binding var allowOrNot: Bool
    
    func makeUIView(context: Context) -> SCNView {
        let sceneView = SCNView(frame: .zero)
        let scene = SCNScene(named: "art.scnassets/ship.scn")
        sceneView.scene = scene
        return sceneView            
    }
    func updateUIView(_ uiView: SCNView, context: Context) {
        uiView.allowsCameraControl = allowOrNot
    }
}


Your approach

If you wanna control a camera's transform using your code, you have to retrieve all the sixteen values ​​from the transform matrix (position, orientation and scale) of the scene's default camera node (or from transform of any other SCNCamera node). In both cases, you'll need the sceneView instance.

sceneView.pointOfView?.transform   // SCNMatrix4

In simplified SwiftUI's SceneView init you've got the parameter called pointOfView:

SceneView(scene: SCNScene?, pointOfView: SCNNode?, options: SceneView.Options)
Related