objective-cuibezierpathquartz-core

append arrowhead to UIBezierPath


I need your help:

I am trying to create a graph using UIBezierPaths with variable widths and consisting of a bezier curve with two control points. Now I'd like to add arrow heads to the end (right hand side) of these paths. Is there a way to do this i.e. by appending a subpath with a smaller lineWidth which contains a triangle? here is a sample picture of some paths to which I'd like to add arrowheads:

enter image description here

Thanks for your help!


Solution

  • Let's assume you drew one of arcs like so:

    UIBezierPath *path = [UIBezierPath bezierPath];
    
    [path moveToPoint:point1];
    [path addQuadCurveToPoint:point3 controlPoint:point2];
    
    CAShapeLayer *shape = [CAShapeLayer layer];
    shape.path = path.CGPath;
    shape.lineWidth = 10;
    shape.strokeColor = [UIColor blueColor].CGColor;
    shape.fillColor = [UIColor clearColor].CGColor;
    shape.frame = self.view.bounds;
    [self.view.layer addSublayer:shape];
    

    You can use atan2 to calculate the angle from the control point to the final point:

    CGFloat angle = atan2f(point3.y - point2.y, point3.x - point2.x);
    

    Note, it doesn't really matter if you use quad bezier or cubic, the idea is the same. Calculate the angle from the last control point to the end point.

    You could then put an arrow head by calculating the corners of the triangle like so:

    CGFloat distance = 15.0;
    path = [UIBezierPath bezierPath];
    [path moveToPoint:point3];
    [path addLineToPoint:[self calculatePointFromPoint:point3 angle:angle + M_PI_2 distance:distance]]; // to the right
    [path addLineToPoint:[self calculatePointFromPoint:point3 angle:angle          distance:distance]]; // straight ahead
    [path addLineToPoint:[self calculatePointFromPoint:point3 angle:angle - M_PI_2 distance:distance]]; // to the left
    [path closePath];
    
    shape = [CAShapeLayer layer];
    shape.path = path.CGPath;
    shape.lineWidth = 2;
    shape.strokeColor = [UIColor blueColor].CGColor;
    shape.fillColor = [UIColor blueColor].CGColor;
    shape.frame = self.view.bounds;
    [self.view.layer addSublayer:shape];
    

    Where we use sinf and cosf to calculate the corners like so:

    - (CGPoint)calculatePointFromPoint:(CGPoint)point angle:(CGFloat)angle distance:(CGFloat)distance {
        return CGPointMake(point.x + cosf(angle) * distance, point.y + sinf(angle) * distance);
    }
    

    That yields something looking like:

    enter image description here

    Clearly, just adjust the distance parameters to control the shape of the triangle.