Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I draw an arrow using Core Graphics?

I need to draw line with arrow on its end in my Draw app. I'm not good in trigonometry, so can't solve this problem.

The user put his finger on the screen and draw the line in any direction. So, the arrow should appear on the line end.

like image 488
Sasha Prent Avatar asked Nov 23 '12 12:11

Sasha Prent


3 Answers

Here's a Swift version of my old Objective-C code. It should work in Swift 3.2 and later versions.

extension UIBezierPath {

    static func arrow(from start: CGPoint, to end: CGPoint, tailWidth: CGFloat, headWidth: CGFloat, headLength: CGFloat) -> UIBezierPath {
        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)
    }

}

Here's an example of how you'd call it:

let arrow = UIBezierPath.arrow(from: CGPoint(x: 50, y: 100), to: CGPoint(x: 200, y: 50),
        tailWidth: 10, headWidth: 25, headLength: 40)
like image 153
rob mayoff Avatar answered Nov 01 '22 08:11

rob mayoff


//This is the integration into the view of the previous exemple
//Attach the following class to your view in the xib file

#import <UIKit/UIKit.h>

@interface Arrow : UIView

@end

#import "Arrow.h"
#import "UIBezierPath+dqd_arrowhead.h"

@implementation Arrow
{
    CGPoint startPoint;
    CGPoint endPoint;
    CGFloat tailWidth;
    CGFloat headWidth;
    CGFloat headLength;
    UIBezierPath *path;

}


- (id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super initWithCoder:aDecoder])
    {
        [self setMultipleTouchEnabled:NO];
        [self setBackgroundColor:[UIColor whiteColor]];

    }
    return self;
}

- (void)drawRect:(CGRect)rect {

    [[UIColor redColor] setStroke];
    tailWidth = 4;
    headWidth = 8;
    headLength = 8;
    path = [UIBezierPath dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
                                                  toPoint:(CGPoint)endPoint
                                                tailWidth:(CGFloat)tailWidth
                                                headWidth:(CGFloat)headWidth
                                               headLength:(CGFloat)headLength];
    [path setLineWidth:2.0];

    [path stroke];

}
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    UITouch* touchPoint = [touches anyObject];
    startPoint = [touchPoint locationInView:self];
    endPoint = [touchPoint locationInView:self];


    [self setNeedsDisplay];
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch* touch = [touches anyObject];
    endPoint=[touch locationInView:self];
    [self setNeedsDisplay];
}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch* touch = [touches anyObject];
    endPoint = [touch locationInView:self];
    [self setNeedsDisplay];
}


@end
like image 8
copter244 Avatar answered Nov 01 '22 09:11

copter244


In Swift 3.0 you can achieve this with

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 = CGMutablePath()
    path.addLines(between: points, transform: transform)
    path.closeSubpath()
    return self.init(cgPath: path)
}

}
like image 2
Sujatha Girijala Avatar answered Nov 01 '22 07:11

Sujatha Girijala