Search code examples
swiftswiftuiscenekit

SceneKit change `.allowCameraControl` without refreshing


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


Solution

  • 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)