Skip to content

Instantly share code, notes, and snippets.

@mayoff
Created November 27, 2012 06:44
Show Gist options
  • Select an option

  • Save mayoff/4152776 to your computer and use it in GitHub Desktop.

Select an option

Save mayoff/4152776 to your computer and use it in GitHub Desktop.

Revisions

  1. mayoff created this gist Nov 27, 2012.
    15 changes: 15 additions & 0 deletions ArrowLayer.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,15 @@
    #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
    126 changes: 126 additions & 0 deletions ArrowLayer.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,126 @@
    #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