Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
202 lines (170 sloc) 6.2 KB
/*
Erica Sadun, http://ericasadun.com
iPhone Developer's Cookbook, 6.x Edition
BSD License, Use at your own risk
*/
#import "UIBezierPath-Points.h"
#define POINTSTRING(_CGPOINT_) (NSStringFromCGPoint(_CGPOINT_))
#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]]
#define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue]
// Return distance between two points
static float distance (CGPoint p1, CGPoint p2)
{
float dx = p2.x - p1.x;
float dy = p2.y - p1.y;
return sqrt(dx*dx + dy*dy);
}
@implementation UIBezierPath (Points)
void getPointsFromBezier(void *info, const CGPathElement *element)
{
NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;
CGPathElementType type = element->type;
CGPoint *points = element->points;
if (type != kCGPathElementCloseSubpath)
{
if ((type == kCGPathElementAddLineToPoint) ||
(type == kCGPathElementMoveToPoint))
[bezierPoints addObject:VALUE(0)];
else if (type == kCGPathElementAddQuadCurveToPoint)
[bezierPoints addObject:VALUE(1)];
else if (type == kCGPathElementAddCurveToPoint)
[bezierPoints addObject:VALUE(2)];
}
}
- (NSArray *)points
{
NSMutableArray *points = [NSMutableArray array];
CGPathApply(self.CGPath, (__bridge void *)points, getPointsFromBezier);
return points;
}
// Return a Bezier path buit with the supplied points
+ (UIBezierPath *) pathWithPoints: (NSArray *) points
{
UIBezierPath *path = [UIBezierPath bezierPath];
if (points.count == 0) return path;
[path moveToPoint:POINT(0)];
for (int i = 1; i < points.count; i++)
[path addLineToPoint:POINT(i)];
return path;
}
- (CGFloat) length
{
NSArray *points = self.points;
float totalPointLength = 0.0f;
for (int i = 1; i < points.count; i++)
totalPointLength += distance(POINT(i), POINT(i-1));
return totalPointLength;
}
- (NSArray *) pointPercentArray
{
// Use total length to calculate the percent of path consumed at each control point
NSArray *points = self.points;
int pointCount = points.count;
float totalPointLength = self.length;
float distanceTravelled = 0.0f;
NSMutableArray *pointPercentArray = [NSMutableArray array];
[pointPercentArray addObject:@(0.0)];
for (int i = 1; i < pointCount; i++)
{
distanceTravelled += distance(POINT(i), POINT(i-1));
[pointPercentArray addObject:@(distanceTravelled / totalPointLength)];
}
// Add a final item just to stop with. Probably not needed.
[pointPercentArray addObject:[NSNumber numberWithFloat:1.1f]]; // 110%
return pointPercentArray;
}
- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope
{
NSArray *points = self.points;
NSArray *percentArray = self.pointPercentArray;
CFIndex lastPointIndex = points.count - 1;
if (!points.count)
return CGPointZero;
// Check for 0% and 100%
if (percent <= 0.0f) return POINT(0);
if (percent >= 1.0f) return POINT(lastPointIndex);
// Find a corresponding pair of points in the path
CFIndex index = 1;
while ((index < percentArray.count) &&
(percent > ((NSNumber *)percentArray[index]).floatValue))
index++;
// This should not happen.
if (index > lastPointIndex) return POINT(lastPointIndex);
// Calculate the intermediate distance between the two points
CGPoint point1 = POINT(index -1);
CGPoint point2 = POINT(index);
float percent1 = [[percentArray objectAtIndex:index - 1] floatValue];
float percent2 = [[percentArray objectAtIndex:index] floatValue];
float percentOffset = (percent - percent1) / (percent2 - percent1);
float dx = point2.x - point1.x;
float dy = point2.y - point1.y;
// Store dy, dx for retrieving arctan
if (slope) *slope = CGPointMake(dx, dy);
// Calculate new point
CGFloat newX = point1.x + (percentOffset * dx);
CGFloat newY = point1.y + (percentOffset * dy);
CGPoint targetPoint = CGPointMake(newX, newY);
return targetPoint;
}
void getBezierElements(void *info, const CGPathElement *element)
{
NSMutableArray *bezierElements = (__bridge NSMutableArray *)info;
CGPathElementType type = element->type;
CGPoint *points = element->points;
switch (type)
{
case kCGPathElementCloseSubpath:
[bezierElements addObject:@[@(type)]];
break;
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
[bezierElements addObject:@[@(type), VALUE(0)]];
break;
case kCGPathElementAddQuadCurveToPoint:
[bezierElements addObject:@[@(type), VALUE(0), VALUE(1)]];
break;
case kCGPathElementAddCurveToPoint:
[bezierElements addObject:@[@(type), VALUE(0), VALUE(1), VALUE(2)]];
break;
}
}
- (NSArray *) bezierElements
{
NSMutableArray *elements = [NSMutableArray array];
CGPathApply(self.CGPath, (__bridge void *)elements, getBezierElements);
return elements;
}
+ (UIBezierPath *) pathWithElements: (NSArray *) elements
{
UIBezierPath *path = [UIBezierPath bezierPath];
if (elements.count == 0) return path;
for (NSArray *points in elements)
{
if (!points.count) continue;
CGPathElementType elementType = [points[0] integerValue];
switch (elementType)
{
case kCGPathElementCloseSubpath:
[path closePath];
break;
case kCGPathElementMoveToPoint:
if (points.count == 2)
[path moveToPoint:POINT(1)];
break;
case kCGPathElementAddLineToPoint:
if (points.count == 2)
[path addLineToPoint:POINT(1)];
break;
case kCGPathElementAddQuadCurveToPoint:
if (points.count == 3)
[path addQuadCurveToPoint:POINT(2) controlPoint:POINT(1)];
break;
case kCGPathElementAddCurveToPoint:
if (points.count == 4)
[path addCurveToPoint:POINT(3) controlPoint1:POINT(1) controlPoint2:POINT(2)];
break;
}
}
return path;
}
@end
Something went wrong with that request. Please try again.