You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift SceneKit:仅检测点击手势并获取3D位置的方法

在SceneKit地球仪中仅响应点击并获取3D坐标

方案一:使用UITapGestureRecognizer(推荐)

你之前认为Tap Gesture Recognizer无法获取3D坐标是误解——其实可以通过手势触发的位置,和touchesBegan中一样执行hitTest或unprojectPoint操作。具体步骤:

  1. 在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)
}
  1. 实现手势处理方法并获取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)")
        }
    }
}
  1. 让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 21:19:58