CAShapeLayer在Swift 3中点击检测

时间:2017-10-09 19:03:40

标签: ios swift cashapelayer hittest

我有一个CAShapeLayer我已经将填充颜色标记为清晰。

当我点击该行时,它并不总是检测CAShapeLayer cgpath是否包含点击点。我的代码如下:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        guard let point = touch?.location(in: self) else { return }

        for sublayer in self.layer.sublayers! {
            if let l = sublayer as? CAShapeLayer {
               if let path = l.path, path.contains(point) {
                    print("Tap detected")
               }
            }
         }
}

在某些情况下,如果我真的点击该线上的中心,它会检测到它。 enter image description here

所以我想把这条线从6到45变得非常胖。但它仍然不起作用。然后我想在此之后将填充变为灰色,现在当我点击填充灰色时它始终检测到水龙头。我真的很困惑,为什么它会检测到线上填充或线的中心,为什么不在线的整个厚度上。

enter image description here

2 个答案:

答案 0 :(得分:0)

我认为问题在于CGPath.contains()无法以您期望的方式运作。

来自Apple的CGPath docs

  

讨论

     

如果路径填充时,点将包含在路径中,如果它将位于绘制区域内。

因此,该方法实际上并未检查您是否与绘制的线相交,它正在检查您是否与形状相交(即使您不是明确填写路径。)

一些基本实验表明,如果提供的点是

,则该方法返回true
  1. 在行/路径的中间位置完全(不包括CAShapeLayer的{​​{1}}中的行的绘制轮廓)或
  2. 位于lineWidth可以创建实体形状的位置(就好像它已经关闭并填充)。
  3. 您可能会在Stack Overflow上找到一些解决方法(似乎很多其他人之前遇到过相同的问题)。例如。 Hit detection when drawing lines in iOS

    您可能还会发现此博文有用:CGPath Hit Testing - Ole Begemann

答案 1 :(得分:0)

迅速4,答案基于说明,并链接至caseynolan在其他评论中的CGPath Hit Testing - Ole Begemann(2012):

来自Ole Begemann博客:

contains(point: CGPoint)
  

如果您想在整个区域进行测试,此功能很有用   路径覆盖。因此,包含(点:CGPoint)   未封闭的路径,因为那些内部没有   填充。

copy(strokingWithWidth lineWidth: CGFloat, lineCap: CGLineCap, lineJoin: CGLineJoin, miterLimit: CGFloat, transform: CGAffineTransform = default) -> CGPath
  

此函数创建一个镜像tapTarget对象,该对象仅包含   路径的描边区域。当用户点击屏幕时,我们   遍历敲击目标而不是实际形状。


我的代码解决方案

我使用链接到功能tap()的UITapGestureRecognizer:

var tappedLayers = [CAShapeLayer]()

@IBAction func tap(_ sender: UITapGestureRecognizer) {        
    let point = sender.location(in: imageView)

    guard let sublayers = imageView.layer.sublayers as? [CAShapeLayer] else {
        return
    }

    for layer in sublayers {
        // create tapTarget for path
        if let target = tapTarget(for: layer) {
            if target.contains(point) {
                tappedLayers.append(layer)
            }
        }
    }
}

fileprivate func tapTarget(for layer: CAShapeLayer) -> UIBezierPath? {
    guard let path = layer.path else {
        return nil
    }

    let targetPath = path.copy(strokingWithWidth: layer.lineWidth, lineCap: CGLineCap.round, lineJoin: CGLineJoin.round, miterLimit: layer.miterLimit)

    return UIBezierPath.init(cgPath: targetPath)
}