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

在macOS中如何用Swift检测点击的是哪个CAShapeLayer?

点击选中并修改CAShapeLayer的问题解决建议

我有一个包含多个CAShapeLayer对象(也可能存在其他CALayer对象)的视图,想要修改用户点击的CAShapeLayer对象。尝试了以下两种方法但均未生效,传递给函数的point参数是NSEvent.locationInWindow,不确定是否需要针对CAShapeLayer进行坐标转换。

尝试过的方法

方法一

private func modifyDrawing(at point: NSPoint) {     
   for layer in view.layer!.sublayers! {
       let s = layer.hitTest(point)
       if s != nil && s is CAShapeLayer {
           selectedShape = s as? CAShapeLayer
       }
   }
   // modify some properties
   selectedShape?.shadowRadius = 20
   selectedShape?.shadowOpacity = 1
   selectedShape?.shadowColor = CGColor.black
}

方法二

private func modifyDrawing(at point: NSPoint) {
    let drawingsAtMouseClick: [CAShapeLayer] = view.layer!.sublayers!.compactMap{ $0 as? CAShapeLayer }

    if drawingsAtMouseClick.isEmpty {        
        return
    }

    for drawing in drawingsAtMouseClick {
        if drawing.contains(point) {
            selectedShape = drawing
            break
        }
    }
    // modify some properties
    selectedShape?.shadowRadius = 20
    selectedShape?.shadowOpacity = 1
    selectedShape?.shadowColor = CGColor.black
}

问题核心

两种方法失效的关键原因是坐标系统不匹配:NSEvent.locationInWindow是窗口坐标系下的点,而CALayer的hitTest(_:)和contains(_:)方法需要的是该layer自身本地坐标系下的点,直接传递窗口坐标会导致坐标错位,无法正确命中目标CAShapeLayer。

修正方案

需要把窗口坐标经过两次转换,拿到对应layer的本地坐标后再做命中检测:

  1. 先将窗口坐标转换为视图坐标系的点
  2. 再将视图坐标转换为目标layer的本地坐标

改进后的方法一(基于hitTest)

private func modifyDrawing(at windowPoint: NSPoint) {
    // 转换窗口坐标到视图坐标
    let viewPoint = view.convert(windowPoint, from: nil)
    
    for layer in view.layer!.sublayers! {
        // 转换视图坐标到当前layer的本地坐标
        let layerPoint = layer.convert(viewPoint, from: view.layer)
        if let hitLayer = layer.hitTest(layerPoint) as? CAShapeLayer {
            selectedShape = hitLayer
            break // 找到第一个命中的layer就停止遍历
        }
    }
    
    // 修改选中layer的属性
    selectedShape?.shadowRadius = 20
    selectedShape?.shadowOpacity = 1
    selectedShape?.shadowColor = CGColor.black
}

改进后的方法二(基于路径精确命中)

如果需要精确命中CAShapeLayer的路径(而不是仅仅命中layer的bounds范围),可以用路径的contains(_:)方法判断:

private func modifyDrawing(at windowPoint: NSPoint) {
    let viewPoint = view.convert(windowPoint, from: nil)
    let shapeLayers = view.layer!.sublayers!.compactMap { $0 as? CAShapeLayer }
    
    guard !shapeLayers.isEmpty else { return }
    
    for shapeLayer in shapeLayers {
        let layerPoint = shapeLayer.convert(viewPoint, from: view.layer)
        // 优先判断路径是否包含点,再判断bounds(兼容路径超出bounds的情况)
        if shapeLayer.path?.contains(layerPoint) == true || shapeLayer.contains(layerPoint) {
            selectedShape = shapeLayer
            break
        }
    }
    
    // 修改选中layer的属性
    selectedShape?.shadowRadius = 20
    selectedShape?.shadowOpacity = 1
    selectedShape?.shadowColor = CGColor.black
}

额外说明

  • hitTest(_:)会自动考虑layer的isHidden、opacity(低于0.01时不会被命中)、mask等属性,是更全面的命中检测方式。
  • 如果你的CAShapeLayer设置了transform(比如缩放、旋转),坐标转换也会自动适配,不需要额外处理。

内容的提问来源于stack exchange,提问作者Ram Patra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 12:15:41