关于C#:如何获得与CGPath的距离以进行命中测试?

How to get the distance to a CGPath for hit-testing?

我有一个开放的CGPath / UIBezierPath,我想为其检测用户是否触摸了它,即一个点是否在距路径一定距离内。 路径是开放的(即线/曲线,而不是形状)。 它可以包含直线和弯曲元素。 我如何获得到路径的距离以进行测试?


似乎CGPath / UIBezierPath都没有执行此操作的功能。 编辑:根据@nielsbot的建议,您可以使用CGPathApply(…)编写自定义实现。 但是,计算到弯曲部分的距离并非易事。

但是,我找到了一种很好的方法来实现我的最初目标,对路径进行了测试:CGPathCreateCopyByStrokingPath(…)

1
2
3
4
5
6
7
- (BOOL)isPoint:(CGPoint)p withinDistance:(CGFloat)distance ofPath:(CGPathRef)path
{
    CGPathRef hitPath = CGPathCreateCopyByStrokingPath(path, NULL, distance*2, kCGLineCapRound, kCGLineJoinRound, 0);
    BOOL isWithinDistance = CGPathContainsPoint(hitPath, NULL, p, false);
    CGPathRelease(hitPath);
    return isWithinDistance;
}

为了获得更好的性能,您可以缓存hitPath。 通过使用CGPathAddPath(…)将原始路径添加到hitPath中,也可以用于封闭路径


对于迅速3.0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final func isPoint(point: CGPoint, withinDistance distance: CGFloat, ofPath path: CGPath) -> Bool {

    if let hitPath = CGPath( __byStroking: path,
                             transform: nil,
                             lineWidth: distance,
                             lineCap: CGLineCap.round,
                             lineJoin: CGLineJoin.miter,
                             miterLimit: 0) {

        let isWithinDistance = hitPath.contains(point)
        return isWithinDistance
    }
    return false
}

用:

1
2
let inside = self.isPoint(point: location, withinDistance: 4, ofPath: myPath!)
if inside{...

当然,正如Patrick所言,最好缓存hitPath,而不是每次都创建它。 如果您有UIBezierPath,只需:

1
 let inside = self.isPoint(point: location, withinDistance: 4, ofPath: myUIPath.cgPath!)