【问题标题】:CAShapeLayer tap detection in Swift 3Swift 3 中的 CAShapeLayer 点击检测
【发布时间】:2017-10-09 19:03:39
【问题描述】:

我有一个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")
               }
            }
         }
}

在某些情况下,如果我真的点击了该行的中心,它会检测到它。

所以我想把从 6 到 45 的线做得很粗,但还是不行。然后我想在此之后将填充设置为灰色,当我点击填充灰色时,它总是会检测到点击。我真的很困惑,为什么它会检测到填充点或线的中心点,为什么不检测线的整个厚度。

【问题讨论】:

    标签: ios swift cashapelayer hittest


    【解决方案1】:

    Swift 4,答案基于 caseynolan 在其他评论中的解释和链接到 CGPath Hit Testing - Ole Begemann (2012):

    来自 Ole Begemann 博客:

    contains(point: CGPoint)
    

    如果您想对整个区域进行命中测试,此功能很有帮助 路径覆盖。因此,contains(point: 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)
    }
    

    【讨论】:

      【解决方案2】:

      我认为问题在于CGPath.contains() 的运行方式与您期望的不同。

      来自苹果的CGPath docs

      讨论

      如果一个点在路径被填充时位于绘制区域内,则该点包含在路径中。

      因此,该方法实际上并没有检查您是否与绘制的 line 相交,而是检查您是否与 shape 相交(即使您没有显式填充路径)。

      一些基本实验表明,如果提供的点,该方法返回true

      1. 正好位于线条/路径的中间(不包括从CAShapeLayerlineWidth 绘制的线条轮廓)或
      2. 位于path 将创建实心形状的中间位置(就好像它已被关闭和填充一样)。

      您可能会在 Stack Overflow 上找到一些解决方法(似乎很多其他人以前也遇到过同样的问题)。例如。 Hit detection when drawing lines in iOS.

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多