SwiftUI结合SceneKit:相机缩放与平移手势实现问题求助
解决方案:SwiftUI + SceneKit 双指平移相机实现
核心思路
放弃纯SwiftUI手势的局限,通过UIViewRepresentable封装SceneKit视图,直接在UIKit层面处理手势。SwiftUI的DragGesture在双指场景下易与单指手势冲突,而UIKit的UIPanGestureRecognizer可精准识别双指平移,同时能和已有的单指旋转、双指缩放手势做互斥配置,完全满足自定义控制需求。
具体实现步骤
1. 封装SceneKit视图到UIViewRepresentable
创建SCNViewRepresentable结构体,内部管理SCNView实例,在makeUIView方法中添加所有手势识别器:
struct SCNViewRepresentable: UIViewRepresentable { @Binding var scene: SCNScene? func makeUIView(context: Context) -> SCNView { let scnView = SCNView() scnView.scene = scene scnView.allowsCameraControl = false // 单指旋转物体手势(保留你的原有逻辑,适配UIKit手势) let rotateGesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleRotate(_:))) rotateGesture.minimumNumberOfTouches = 1 rotateGesture.maximumNumberOfTouches = 1 scnView.addGestureRecognizer(rotateGesture) // 双指缩放手势(替换SwiftUI的MagnificationGesture,UIKit实现更稳定) let pinchGesture = UIPinchGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handlePinch(_:))) pinchGesture.minimumNumberOfTouches = 2 pinchGesture.maximumNumberOfTouches = 2 scnView.addGestureRecognizer(pinchGesture) // 双指平移相机XY轴手势 let panGesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleCameraPan(_:))) panGesture.minimumNumberOfTouches = 2 panGesture.maximumNumberOfTouches = 2 // 设置手势互斥,避免冲突 rotateGesture.require(toFail: panGesture) pinchGesture.require(toFail: panGesture) scnView.addGestureRecognizer(panGesture) scnView.delegate = context.coordinator return scnView } func updateUIView(_ uiView: SCNView, context: Context) { uiView.scene = scene } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, SCNSceneRendererDelegate { var parent: SCNViewRepresentable private var initialCameraPos: SCNVector3? private var initialPanTrans: CGPoint? init(_ parent: SCNViewRepresentable) { self.parent = parent } // 单指旋转物体处理 @objc func handleRotate(_ gesture: UIPanGestureRecognizer) { guard let scnView = gesture.view as? SCNView, let targetNode = scnView.scene?.rootNode.childNodes.first(where: { $0.name == "YourTargetNode" }) else { return } let trans = gesture.translation(in: scnView) let rotation = SCNVector4Make(Float(trans.y)/100, Float(-trans.x)/100, 0, 1) targetNode.transform = SCNMatrix4Mult(targetNode.transform, SCNMatrix4MakeRotation(Float.pi/2, rotation.x, rotation.y, rotation.z)) if gesture.state == .ended { gesture.setTranslation(.zero, in: scnView) } } // 双指缩放相机处理 @objc func handlePinch(_ gesture: UIPinchGestureRecognizer) { guard let camera = parent.scene?.rootNode.camera else { return } switch gesture.state { case .began, .changed: camera.position.z *= Float(gesture.scale) gesture.scale = 1.0 default: break } } // 双指平移相机XY轴处理 @objc func handleCameraPan(_ gesture: UIPanGestureRecognizer) { guard let scnView = gesture.view as? SCNView, let camera = scnView.scene?.rootNode.camera else { return } let trans = gesture.translation(in: scnView) let viewSize = scnView.bounds.size switch gesture.state { case .began: initialCameraPos = camera.position initialPanTrans = trans case .changed: guard let initPos = initialCameraPos, let initTrans = initialPanTrans else { return } // 调整系数适配你的场景坐标系 let xDelta = Float((trans.x - initTrans.x) * 0.01) let yDelta = Float((trans.y - initTrans.y) * 0.01) // 若场景Y轴向上,可反转yDelta符号 camera.position = SCNVector3(initPos.x + xDelta, initPos.y - yDelta, initPos.z) case .ended: initialCameraPos = nil initialPanTrans = nil default: break } } } }
2. 在SwiftUI视图中使用封装组件
struct ContentView: View { @State private var scene: SCNScene? = SCNScene(named: "YourScene.scn") var body: some View { SCNViewRepresentable(scene: $scene) .ignoresSafeArea() } }
关键注意点
- 手势互斥:通过
require(toFail:)确保双指手势仅在单指手势不触发时生效,彻底避免冲突。 - 坐标系适配:SceneKit相机坐标系与UIKit屏幕坐标系方向可能差异,需根据你的场景调整平移的正负号和系数。
- 状态记录:手势开始时记录相机初始位置和手势初始位移,防止平移过程中出现跳变。
- 性能优化:若场景复杂,可在手势处理中添加
scnView.displayIfNeeded(),或用SCNTransaction包裹相机属性更新,保证流畅性。
内容的提问来源于stack exchange,提问作者HBE
相关产品推荐
相关产品推荐

