Swift SceneKit:仅检测点击手势并获取3D位置的方法
在SceneKit地球仪中仅响应点击并获取3D坐标
方案一:使用UITapGestureRecognizer(推荐)
你之前认为Tap Gesture Recognizer无法获取3D坐标是误解——其实可以通过手势触发的位置,和touchesBegan中一样执行hitTest或unprojectPoint操作。具体步骤:
- 在
setupScene方法中添加点击手势识别器:
private func setupScene() { let scene = SCNScene() sceneView = SCNView(frame: view.frame) sceneView.scene = scene sceneView.showsStatistics = true sceneView.backgroundColor = .clear sceneView.allowsCameraControl = true sceneView.isUserInteractionEnabled = true // 添加点击手势识别器 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleGlobeTap(_:))) // 允许与SceneView自带的拖拽/缩放手势同时识别(保留相机控制功能) tapGesture.delegate = self sceneView.addGestureRecognizer(tapGesture) self.view.addSubview(sceneView) }
- 实现手势处理方法并获取3D坐标:
@objc private func handleGlobeTap(_ gesture: UITapGestureRecognizer) { guard gesture.state == .ended else { return } let touchLocation = gesture.location(in: sceneView) // 执行Hit Test获取点击的节点 let hitTestResults = sceneView.hitTest(touchLocation, options: nil) // 转换屏幕坐标为3D空间坐标 let touchLocation3D = sceneView.unprojectPoint(SCNVector3(Float(touchLocation.x), Float(touchLocation.y), 0.0)) print("点击的3D坐标:\(touchLocation3D)") if let tappedNode = hitTestResults.first?.node { print("点击的节点:\(tappedNode)") if tappedNode.name == "NewYorkDot" { print("点击纽约!位置:\(tappedNode.position)") } else if tappedNode.name == "RegularDot" { print("点击普通点!位置:\(tappedNode.position)") } } }
- 让
GlobeViewController遵循UIGestureRecognizerDelegate协议:
extension GlobeViewController: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { // 允许点击手势与相机控制的拖拽/缩放手势同时工作 return true } }
方案二:过滤touchesBegan事件仅处理点击
如果坚持使用touchesBegan方法,可以通过判断触摸属性过滤非点击事件:
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first, touch.view == self.sceneView else { return } // 仅处理单次点击 guard touch.tapCount == 1 else { return } let initialLocation = touch.location(in: sceneView) // 延迟检查触摸是否发生移动,排除拖拽操作 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in guard let self = self else { return } let currentLocation = touch.location(in: self.sceneView) let distance = sqrt(pow(currentLocation.x - initialLocation.x, 2) + pow(currentLocation.y - initialLocation.y, 2)) // 移动距离小于阈值则判定为点击 if distance < 5 { let hitTestResults = self.sceneView.hitTest(initialLocation, options: nil) let touchLocation3D = self.sceneView.unprojectPoint(SCNVector3(Float(initialLocation.x), Float(initialLocation.y), 0.0)) print("点击的3D坐标:\(touchLocation3D)") if let tappedNode = hitTestResults.first?.node { print("点击的节点:\(tappedNode)") if tappedNode.name == "NewYorkDot" { print("点击纽约!位置:\(tappedNode.position)") } else if tappedNode.name == "RegularDot" { print("点击普通点!位置:\(tappedNode.position)") } } } } }
注意:这种方法需要处理触摸移动的判断,不如手势识别器简洁可靠。
内容的提问来源于stack exchange,提问作者Ahmed Zaidan
相关产品推荐
相关产品推荐

