SceneKit+SwiftUI相机无法重置问题求助
SceneKit + SwiftUI 相机重置无效问题解决
问题根源
- 重复加载场景:初始代码中
loadScene()直接作为SceneView参数,SwiftUI视图刷新时会重复调用该函数,导致相机节点和默认状态被反复重置,修改操作失效。 - 相机控制器冲突:开启
.allowsCameraControl后,SceneKit内置的相机控制器会持续接管相机节点的transform属性,直接赋值transform或替换节点的操作会被控制器覆盖,无法生效。 - 状态绑定不及时:更新后的代码替换相机节点后,SwiftUI的
pointOfView绑定未触发SceneView实时更新,导致新相机节点未被正确识别。
解决方案
核心思路:保留原相机节点,临时禁用相机控制器以修改状态,再恢复控制,同时确保场景仅加载一次。
关键修改点
- 用
@State存储场景实例,通过.task仅加载一次,避免重复创建。 - 保存相机的默认
position和eulerAngles(或完整transform),作为重置基准。 - 重置时,先关闭相机控制,通过
SCNTransaction执行动画修改相机状态,完成后重新启用控制。
修正后的完整代码
import SwiftUI import SceneKit struct ContentView: View { @State private var scene: SCNScene? @State private var mainCameraNode: SCNNode? @State private var defaultCameraPosition: SCNVector3? @State private var defaultCameraEulerAngles: SCNVector3? @State private var allowCameraControl = true private func createCameraNode() -> SCNNode { let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.camera?.zNear = 1 cameraNode.camera?.zFar = 10000 let screenSize = UIScreen.main.bounds.size let screenAspectRatio = Float(screenSize.width / screenSize.height) let baseDistance: Float = 1000 let heightFactor: Float = 0.3 let xOffset = -baseDistance * screenAspectRatio * 1.5 let yOffset = baseDistance * heightFactor let zOffset = -baseDistance cameraNode.position = SCNVector3(x: xOffset, y: yOffset, z: zOffset) cameraNode.look(at: SCNVector3(0, 0, 0)) // 保存默认状态 defaultCameraPosition = cameraNode.position defaultCameraEulerAngles = cameraNode.eulerAngles return cameraNode } private func loadScene() -> SCNScene { let scene = SCNScene(named: "myModel.scn") ?? SCNScene() let cameraNode = createCameraNode() scene.rootNode.addChildNode(cameraNode) mainCameraNode = cameraNode return scene } private func resetCameraPosition() { guard let cameraNode = mainCameraNode, let defaultPos = defaultCameraPosition, let defaultAngles = defaultCameraEulerAngles else { print("Error: Camera or default state is nil!") return } // 临时禁用相机控制,避免冲突 allowCameraControl = false // 用事务实现平滑动画 SCNTransaction.begin() SCNTransaction.animationDuration = 1.0 // 动画完成后恢复相机控制 SCNTransaction.completionBlock = { allowCameraControl = true } cameraNode.position = defaultPos cameraNode.eulerAngles = defaultAngles SCNTransaction.commit() } var body: some View { ZStack { SceneView( scene: scene, pointOfView: mainCameraNode, options: [.autoenablesDefaultLighting], cameraControlConfiguration: allowCameraControl ? .init() : nil ) .ignoresSafeArea() .task { scene = loadScene() } VStack { Spacer() Button { resetCameraPosition() } label: { Text("Reset Position") .padding() .background(Color.white) .cornerRadius(10) } .padding(.bottom, 20) } } } } #Preview { ContentView() }
自动重置扩展(实现3秒自动恢复)
添加相机状态监听,当用户调整相机后启动定时器,超时后自动重置:
// 将ContentView改为继承NSObject以支持KVO class ContentView: NSObject, View { @State private var scene: SCNScene? @State private var mainCameraNode: SCNNode? @State private var defaultCameraPosition: SCNVector3? @State private var defaultCameraEulerAngles: SCNVector3? @State private var allowCameraControl = true @State private var resetTimer: Timer? private func createCameraNode() -> SCNNode { let cameraNode = SCNNode() // ... 原有代码 ... // 添加transform变化监听 cameraNode.addObserver(self, forKeyPath: "transform", options: [.new], context: nil) return cameraNode } // KVO回调 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "transform" { // 用户操作相机,重置定时器 resetTimer?.invalidate() resetTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { [weak self] _ in self?.resetCameraPosition() } } } var body: some View { ZStack { // ... 原有SceneView代码 ... } .onDisappear { // 清理监听和定时器 mainCameraNode?.removeObserver(self, forKeyPath: "transform") resetTimer?.invalidate() } } }
内容的提问来源于stack exchange,提问作者Matteo Tripolt
相关产品推荐
相关产品推荐

