I have an open CGPath/UIBezierPath for which I want to detect if the user touches it, i.e. whether a point is within a certain distance from the path. The path is open (i.e. a line/curve, not a shape). It can contain both straight & curved elements. How do I get the distance to the path to hit-test it?
It seems neither CGPath/UIBezierPath has a function to do this. EDIT: As per @nielsbot's suggestion, you could write a custom implementation using CGPathApply(…)
. Computing the distance to the curved parts is not so trivial though.
However I have found a neat way to achieve my original goal, hit-testing the path: CGPathCreateCopyByStrokingPath(…)
.
- (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;
}
For better performance, you can cache hitPath. Could also be used for closed paths by adding the original path to hitPath using CGPathAddPath(…)
for swift 3.0:
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
}
use:
let inside = self.isPoint(point: location, withinDistance: 4, ofPath: myPath!)
if inside{...
of course, as Patrick noted, is better to cache hitPath, instead of creating it every time. In case You have UIBezierPath, simply:
let inside = self.isPoint(point: location, withinDistance: 4, ofPath: myUIPath.cgPath!)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With