I have worked on this quite a while, and would like to share my answer (I'm the one who asked the question btw).
- I want to detect touches along a path (e.g a Bezier Curve). Problem is that it can only tell you if a touch hit the path, but not where (using
CGPathContainsPoint
).
- To avoid having the user hit exactly on the path, create a fatter version with
CGPathCreateCopyByStrokingPath
), so that's the one to check for touch on.
- The main problem still is, where along the path did the touch hit. What I did was to create a dashed path copy with
CGPathCreateCopyByDashingPath
with minimal spacing. This path is never actually displayed. Then I got every part of it with the CGPathApply
saving every path element in an array.
- When a touch is detected, and is within the "fat path", then I just loop through the array of path-dash-elements, checking the distance (just regular Pythagoras) to the start-point of each path-dash-element.
If the touch-point is closest to the 14'th element, and there are e.g. 140 elements total, that means we're 10% along the way.
- Now that we have the percentage. What I want now is to paint the path as far as the touch. I tried to use the suggested
strokeEnd
property on a path. That didn't match my calculated percentage at all. Actually, it seemed to me to be quite inaccurate. Or maybe my calculation is.
Anyway, since we already have all the path-elements, and know which one to stop at (the 14th element), what I did was to build a new path using the path-elements. Since I didn't want a dashed path (that was just a trick to find out where the touch was on the long continuous path), I just skipped all the moveTo path-elements (except the very first). That will create a continous path.
CGPathApply - by request
// MARK: - Stuff for CGPathApply
typealias MyPathApplier = @convention(block) (UnsafePointer<CGPathElement>) -> Void
// http://stackoverflow.com/questions/24274913/equivalent-of-or-alternative-to-cgpathapply-in-swift
// Note: You must declare MyPathApplier as @convention(block), because
// if you don't, you get "fatal error: can't unsafeBitCast between
// types of different sizes" at runtime, on Mac OS X at least.
func myPathApply(path: CGPath!, block: MyPathApplier) {
let callback: @convention(c) (UnsafeMutablePointer<Void>, UnsafePointer<CGPathElement>) -> Void = { (info, element) in
let block = unsafeBitCast(info, MyPathApplier.self)
block(element)
}
CGPathApply(path, unsafeBitCast(block, UnsafeMutablePointer<Void>.self), unsafeBitCast(callback, CGPathApplierFunction.self))
}