Skip to content

Instantly share code, notes, and snippets.

@mayoff mayoff/Arrow.swift
Last active Oct 29, 2019

Embed
What would you like to do?
UIBezierPath category to create an arrow (now with a Swift version!)
// Swift 2.2 syntax / API
import UIKit
extension UIBezierPath {
class func arrow(from start: CGPoint, to end: CGPoint, tailWidth: CGFloat, headWidth: CGFloat, headLength: CGFloat) -> Self {
let length = hypot(end.x - start.x, end.y - start.y)
let tailLength = length - headLength
func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint { return CGPoint(x: x, y: y) }
var points: [CGPoint] = [
p(0, tailWidth / 2),
p(tailLength, tailWidth / 2),
p(tailLength, headWidth / 2),
p(length, 0),
p(tailLength, -headWidth / 2),
p(tailLength, -tailWidth / 2),
p(0, -tailWidth / 2)
]
let cosine = (end.x - start.x) / length
let sine = (end.y - start.y) / length
var transform = CGAffineTransform(a: cosine, b: sine, c: -sine, d: cosine, tx: start.x, ty: start.y)
let path = CGPathCreateMutable()
CGPathAddLines(path, &transform, &points, points.count)
CGPathCloseSubpath(path)
return self.init(CGPath: path)
}
}
// Example call:
let arrow = UIBezierPath.arrow(from: CGPointMake(50, 100), to: CGPointMake(200, 50),
tailWidth: 10, headWidth: 25, headLength: 40)
#import <UIKit/UIKit.h>
@interface UIBezierPath (dqd_arrowhead)
+ (UIBezierPath *)dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
toPoint:(CGPoint)endPoint
tailWidth:(CGFloat)tailWidth
headWidth:(CGFloat)headWidth
headLength:(CGFloat)headLength;
@end
// To the extent possible under law, I (Rob Mayoff, the author of this work) have waived
// all copyright and related or neighboring rights to this work, in accordance with
// the CC0 1.0 Universal Public Domain Dedication. Please see this page
// for details: http://creativecommons.org/publicdomain/zero/1.0/
#import "UIBezierPath+dqd_arrowhead.h"
#define kArrowPointCount 7
@implementation UIBezierPath (dqd_arrowhead)
+ (UIBezierPath *)dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
toPoint:(CGPoint)endPoint
tailWidth:(CGFloat)tailWidth
headWidth:(CGFloat)headWidth
headLength:(CGFloat)headLength {
CGFloat length = hypotf(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
CGPoint points[kArrowPointCount];
[self dqd_getAxisAlignedArrowPoints:points
forLength:length
tailWidth:tailWidth
headWidth:headWidth
headLength:headLength];
CGAffineTransform transform = [self dqd_transformForStartPoint:startPoint
endPoint:endPoint
length:length];
CGMutablePathRef cgPath = CGPathCreateMutable();
CGPathAddLines(cgPath, &transform, points, sizeof points / sizeof *points);
CGPathCloseSubpath(cgPath);
UIBezierPath *uiPath = [UIBezierPath bezierPathWithCGPath:cgPath];
CGPathRelease(cgPath);
return uiPath;
}
+ (void)dqd_getAxisAlignedArrowPoints:(CGPoint[kArrowPointCount])points
forLength:(CGFloat)length
tailWidth:(CGFloat)tailWidth
headWidth:(CGFloat)headWidth
headLength:(CGFloat)headLength {
CGFloat tailLength = length - headLength;
points[0] = CGPointMake(0, tailWidth / 2);
points[1] = CGPointMake(tailLength, tailWidth / 2);
points[2] = CGPointMake(tailLength, headWidth / 2);
points[3] = CGPointMake(length, 0);
points[4] = CGPointMake(tailLength, -headWidth / 2);
points[5] = CGPointMake(tailLength, -tailWidth / 2);
points[6] = CGPointMake(0, -tailWidth / 2);
}
+ (CGAffineTransform)dqd_transformForStartPoint:(CGPoint)startPoint
endPoint:(CGPoint)endPoint
length:(CGFloat)length {
CGFloat cosine = (endPoint.x - startPoint.x) / length;
CGFloat sine = (endPoint.y - startPoint.y) / length;
return (CGAffineTransform){ cosine, sine, -sine, cosine, startPoint.x, startPoint.y };
}
@end
@mayoff

This comment has been minimized.

Copy link
Owner Author

mayoff commented Nov 26, 2012

@Prakashan

This comment has been minimized.

Copy link

Prakashan commented Jan 10, 2013

How to use it in a View?

@pragmatrix

This comment has been minimized.

Copy link

pragmatrix commented Sep 1, 2013

Nice work! For anyone interested, here is a MonoTouch / C# port: https://gist.github.com/pragmatrix/6406094

@mwermuth

This comment has been minimized.

Copy link

mwermuth commented Sep 11, 2014

I made a Swift extension for this :) https://gist.github.com/mwermuth/07825df27ea28f5fc89a

@misbell

This comment has been minimized.

Copy link

misbell commented Nov 7, 2014

bless the beasts and the children, and the developers who convert code to Xamarin and Swift.

However, I rewrote the Monotouch example to use CGPoint rather than PointF, because it didn't compile, failing on the AddLines call, not liking the PointF[].

Note: I am compiling using the new Unified 64 bit compiler for OSX, not MonoTouch, so perhaps there is a difference in what will and will not work.

static class ArrowPath
{
    public static CGPath pathWithArrowFromPoint (
        PointF startPoint,
        PointF endPoint,
        float tailWidth,
        float headWidth,
        float headLength)
    {
        var dx = endPoint.X - startPoint.X;
        var dy = endPoint.Y - startPoint.Y;
        var length = (float)Math.Sqrt (dx * dx + dy * dy);
        var points = getAxisAlignedArrowPoints (length, tailWidth, headWidth, headLength);
        var transform = transformForStartPoint (startPoint, endPoint, length);

        var path = new CGPath ();
        path.AddLines (transform, points);
        path.CloseSubpath ();

        return path;
    }

    static CGPoint[] getAxisAlignedArrowPoints (
        float length,
        float tailWidth, 
        float headWidth, 
        float headLength)
    {
        var tailLength = length - headLength;
        var points = new CGPoint[7];

        points [0] = new CGPoint (0, tailWidth / 2);
        points [1] = new CGPoint (tailLength, tailWidth / 2);
        points [2] = new CGPoint (tailLength, headWidth / 2);
        points [3] = new CGPoint (length, 0);
        points [4] = new CGPoint (tailLength, -headWidth / 2);
        points [5] = new CGPoint (tailLength, -tailWidth / 2);
        points [6] = new CGPoint (0, -tailWidth / 2);

        return points;
    }

    static CGAffineTransform transformForStartPoint (CGPoint startPoint, CGPoint endPoint, float length)
    {
        var cosine = (endPoint.X - startPoint.X) / length;
        var sine = (endPoint.Y - startPoint.Y) / length;
        return new CGAffineTransform (cosine, sine, -sine, cosine, startPoint.X, startPoint.Y);
    }
}
@rajatmohanty

This comment has been minimized.

Copy link

rajatmohanty commented Apr 21, 2015

@Prakashan

You need to import "UIBezierPath+dqd_arrowhead.h" file

Then you can use it like this Ex

  • (void)viewDidLoad {

    [super viewDidLoad];
    [self drawArrow];

}
-(void)drawArrow{

UIBezierPath *path=[UIBezierPath dqd_bezierPathWithArrowFromPoint:CGPointMake(50, 50)
                                                          toPoint:CGPointMake(70, 80)
                                                        tailWidth:20.0f
                                                        headWidth:10.0f
                                                       headLength:10.0f];

CAShapeLayer *shape = [CAShapeLayer layer];
shape.path = path.CGPath;
shape.fillColor = [UIColor colorWithRed:255/255.0 green:20/255.0 blue:147/255.0 alpha:1].CGColor;

[self.view.layer addSublayer:shape];

}

@csann

This comment has been minimized.

Copy link

csann commented Sep 5, 2016

Anyone know how to convert Arrow.swift to Swift 3 syntax?

I'm having trouble with CGPathAddLines(path, &transform, &points, points.count)

@PollyP

This comment has been minimized.

Copy link

PollyP commented Nov 6, 2016

Super awesome! Thanks for this gist, @mayoff!

Here is my Swift 3.0 version:

// Swift 3.0 syntax/api
import UIKit

extension UIBezierPath {
    
    class func arrow(from start: CGPoint, to end: CGPoint, tailWidth: CGFloat, headWidth: CGFloat, headLength: CGFloat) -> Self {
        let length = hypot(end.x - start.x, end.y - start.y)
        let tailLength = length - headLength
        
        func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint { return CGPoint(x: x, y: y) }
        let points: [CGPoint] = [
            p(0, tailWidth / 2),
            p(tailLength, tailWidth / 2),
            p(tailLength, headWidth / 2),
            p(length, 0),
            p(tailLength, -headWidth / 2),
            p(tailLength, -tailWidth / 2),
            p(0, -tailWidth / 2)
        ]
        
        let cosine = (end.x - start.x) / length
        let sine = (end.y - start.y) / length
        let transform = CGAffineTransform(a: cosine, b: sine, c: -sine, d: cosine, tx: start.x, ty: start.y)
        
        let path = CGMutablePath()
        path.addLines(between: points, transform: transform )
        path.closeSubpath()
        return self.init(cgPath: path)
    }
    
}

Integrating it into a UIView:

override func draw(_ rect: CGRect) {
        let arrow = UIBezierPath.arrow(from: CGPoint(x:10, y:10), to: CGPoint(x:200, y:10),tailWidth: 10, headWidth: 25, headLength: 40)
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = arrow.cgPath
        self.layer.addSublayer(shapeLayer)
    }
@fareast555

This comment has been minimized.

Copy link

fareast555 commented Jun 2, 2017

Just what I needed. Incorporating it into an app now to use as a alternative to a graphic underlay. Thanks a million for sharing this. You rock!!

@rajaramanZencode

This comment has been minimized.

Copy link

rajaramanZencode commented Jun 13, 2018

How to draw without closed head, need only line for tail

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.