Skip to content

Instantly share code, notes, and snippets.

@mayoff
Created November 27, 2012 06:44
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save mayoff/4152776 to your computer and use it in GitHub Desktop.
Save mayoff/4152776 to your computer and use it in GitHub Desktop.
A CALayer subclass that draws a very simple arrow
#import <QuartzCore/QuartzCore.h>
@interface ArrowLayer : CALayer
@property (nonatomic) CGFloat thickness;
@property (nonatomic) CGFloat startRadians;
@property (nonatomic) CGFloat lengthRadians;
@property (nonatomic) CGFloat headLengthRadians;
@property (nonatomic, strong) UIColor *fillColor;
@property (nonatomic, strong) UIColor *strokeColor;
@property (nonatomic) CGFloat lineWidth;
@property (nonatomic) CGLineJoin lineJoin;
@end
#import "ArrowLayer.h"
#import <objc/runtime.h>
@implementation ArrowLayer
@dynamic thickness;
@dynamic startRadians;
@dynamic lengthRadians;
@dynamic headLengthRadians;
@dynamic fillColor;
@dynamic strokeColor;
@dynamic lineWidth;
@dynamic lineJoin;
+ (NSSet *)customPropertyKeys {
static NSMutableSet *set;
static dispatch_once_t once;
dispatch_once(&once, ^{
unsigned int count;
objc_property_t *properties = class_copyPropertyList(self, &count);
set = [[NSMutableSet alloc] initWithCapacity:count];
for (int i = 0; i < count; ++i) {
[set addObject:@(property_getName(properties[i]))];
}
free(properties);
});
return set;
}
+ (BOOL)needsDisplayForKey:(NSString *)key {
return [[self customPropertyKeys] containsObject:key] || [super needsDisplayForKey:key];
}
- (id)initWithLayer:(id)layer {
if (self = [super initWithLayer:layer]) {
for (NSString *key in [self.class customPropertyKeys]) {
[self setValue:[layer valueForKey:key] forKey:key];
}
}
return self;
}
- (BOOL)needsDisplayOnBoundsChange {
return YES;
}
- (void)drawInContext:(CGContextRef)gc {
[self moveOriginToCenterInContext:gc];
[self addArrowToPathInContext:gc];
[self drawPathOfContext:gc];
}
- (void)moveOriginToCenterInContext:(CGContextRef)gc {
CGRect bounds = self.bounds;
CGContextTranslateCTM(gc, CGRectGetMidX(bounds), CGRectGetMidY(bounds));
}
- (void)addArrowToPathInContext:(CGContextRef)gc {
CGFloat startRadians;
CGFloat headRadians;
CGFloat tipRadians;
[self getStartRadians:&startRadians headRadians:&headRadians tipRadians:&tipRadians];
CGFloat thickness = self.thickness;
CGFloat outerRadius = self.bounds.size.width / 2;
CGFloat tipRadius = outerRadius - thickness / 2;
CGFloat innerRadius = outerRadius - thickness;
BOOL outerArcIsClockwise = tipRadians > startRadians;
CGContextMoveToPoint(gc, tipRadius * cosf(tipRadians), tipRadius * sinf(tipRadians));
CGContextAddArc(gc, 0, 0, outerRadius, headRadians, startRadians, outerArcIsClockwise);
CGContextAddArc(gc, 0, 0, innerRadius, startRadians, headRadians, !outerArcIsClockwise);
CGContextClosePath(gc);
}
- (void)getStartRadians:(CGFloat *)startRadiansOut headRadians:(CGFloat *)headRadiansOut tipRadians:(CGFloat *)tipRadiansOut {
*startRadiansOut = self.startRadians;
CGFloat lengthRadians = self.lengthRadians;
CGFloat headLengthRadians = [self clippedHeadLengthRadians];
// Compute headRadians carefully so it is exactly equal to startRadians if the head length was clipped.
*headRadiansOut = *startRadiansOut + (lengthRadians - headLengthRadians);
*tipRadiansOut = *startRadiansOut + lengthRadians;
}
- (CGFloat)clippedHeadLengthRadians {
CGFloat lengthRadians = self.lengthRadians;
CGFloat headLengthRadians = copysignf(self.headLengthRadians, lengthRadians);
if (fabsf(headLengthRadians) > fabsf(lengthRadians)) {
headLengthRadians = lengthRadians;
}
return headLengthRadians;
}
- (void)drawPathOfContext:(CGContextRef)gc {
CGPathDrawingMode mode = 0;
[self setFillPropertiesOfContext:gc andUpdateMode:&mode];
[self setStrokePropertiesOfContext:gc andUpdateMode:&mode];
CGContextDrawPath(gc, mode);
}
- (void)setFillPropertiesOfContext:(CGContextRef)gc andUpdateMode:(CGPathDrawingMode *)modeInOut {
UIColor *fillColor = self.fillColor;
if (fillColor) {
*modeInOut |= kCGPathFill;
CGContextSetFillColorWithColor(gc, fillColor.CGColor);
}
}
- (void)setStrokePropertiesOfContext:(CGContextRef)gc andUpdateMode:(CGPathDrawingMode *)modeInOut {
UIColor *strokeColor = self.strokeColor;
CGFloat lineWidth = self.lineWidth;
if (strokeColor && lineWidth > 0) {
*modeInOut |= kCGPathStroke;
CGContextSetStrokeColorWithColor(gc, strokeColor.CGColor);
CGContextSetLineWidth(gc, lineWidth);
CGContextSetLineJoin(gc, self.lineJoin);
}
}
@end
@mayoff
Copy link
Author

mayoff commented Nov 27, 2012

See http://stackoverflow.com/a/13578767/77567 for an explanation of this code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment