I have working code that solves my problem. I'm not particularly proud of it; the overall technique is essentially a brute-force attack on a UIBezierPath, which is kind of funny if you think about it. (Please don't think about it).
As I mentioned, I have access to a method that allows me to get a point from a given percentage of a line. I have taken advantage of that power to find the closest percentage to the given point by running through 1000 percentage values. To wit:
Start with a CGPoint that represents where on the line the user touched.
let pointA = // the incoming CGPoint
Run through the 0-1 range in the thousands. This is the set of percentages we're going to brute-force and see if we have a match. For each, we run pointAtPercentOfLength
, from the linked project above.
var pointArray:[[String:Any]] = []
for (var i:Int = 0; i <= 1000; i++) {
let value = CGFloat(round((CGFloat(i) / CGFloat(1000)) * 1000) / 1000)
let testPoint = path.pointAtPercentOfLength(value)
let pointB = CGPoint(x: floor(testPoint.x), y: floor(testPoint.y))
pointArray.append(["point" : pointB, "percent" : value])
}
That was the hard part. Now we take the returning values and calculate the distance between each point and the touched point. Closest one is our winner.
// sort the damned array by distance so we find the closest
var distanceArray:[[String:Any]] = []
for point in pointArray {
distanceArray.append([
"distance" : self.distanceFrom(point["point"] as! CGPoint, point2: pointA),
"point" : point["point"],
"percent" : point["percent"] as! CGFloat
])
}
Here's the sorting function if you're interested:
func distanceFrom(point1:CGPoint, point2:CGPoint) -> CGFloat {
let xDist = (point2.x - point1.x);
let yDist = (point2.y - point1.y);
return sqrt((xDist * xDist) + (yDist * yDist));
}
Finally, I sort the array by the distance of the values, and pick out the winner as our closest percent.
let ordered = distanceArray.sort { return CGFloat($0["distance"] as! CGFloat) < CGFloat($1["distance"] as! CGFloat) }
ordered
is a little dictionary that includes percent
, the correct value for a percentage of a line's length.
This is not pretty code, I know. I know. But it gets the job done and doesn't appear to be computationally expensive.
As a postscript, I should point to what appears to be a proper resource for doing this. During my research I read this beautiful article by David Rönnqvist, which included an equation for calculating the percentage distance along a path:
start⋅(1-t)3 + 3⋅c1⋅t(1-t)2 + 3⋅c2⋅t2(1-t) + end⋅t3
I was just about to try implementing that before my final solution occurred to me. Math, man. I can't even brain it. But if you're more ambitious than I, and wish to override my 30 lines of code with a five-line alternative, everyone would appreciate it!