0

I have a CGPath, and on that path I detect touches. The path is basically a line or a curve, but it is not filled. The idea is that the user strokes the path, so wherever the user has stroked along the path I'd like to draw the path with a different stroke/color etc.

First, to find if the touches are inside the path, and to give the user some room for error, I'm using CGPathCreateCopyByStrokingPath, and CGPointInsidePath. This works fine.

Next, I need to find how far along the path the user has stroked. That is today's question.

When that is done, and I have an answer like 33%, then I'd be a simple assignment to strokeEnd on the path. (two copies, one complete path, and one on top with strokeEnd set to whatever percentage of the complete path the user has stroked with his finger so far).

Any ideas how to find how far along the path the user has stroked his finger?

specimen
  • 1,735
  • 14
  • 23

2 Answers2

1

I have worked on this quite a while, and would like to share my answer (I'm the one who asked the question btw).

  1. 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).
  2. 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.
  3. 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.
  4. 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.

  1. 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))
}
specimen
  • 1,735
  • 14
  • 23
0

I have a utility class GHPathUtilies.m which includes a method:

+(CGFloat) totalLengthOfCGPath:(CGPathRef)pathRef

This is all written in Objective-C, but presumably you could use the same technique to walk along the path elements, using CGPathApply, until you are near enough, or have passed the target point. The general idea is to break the curves between each element into small line segments and keep a running sum of the length of each segment.

Of course if the path doubles back on itself, you'll have to figure out which point is the touch point.

Glenn Howes
  • 5,447
  • 2
  • 25
  • 29
  • I already made a similar solution with Apply., which I see you're using as well. To avoid all the math (it's too complicated for me) I used a dashed copy of the path, then all segments are the same (short) length. Will post when it's cleaned up. – specimen Dec 09 '15 at 16:01