Search code examples
iosswift3uibezierpath

How to draw a directional arrow head


I have lines in many directions and angles, I draw them using UIBezierpath.

I need to draw an arrow on one of the ends of the line; dynamically depending on given point.

Edit: hand drawn but should give you a better understandin

Edit 2: with Jake answer here is my code

let y2 = line.point2Y

let path = UIBezierPath()

  path.move(to: CGPoint(x: x1, y: y1))
  path.addLine(to: CGPoint(x: x2, y: y2))
path.addArrow(start: CGPoint(x: x1, y: y1), end: CGPoint(x: x2, y: y2), pointerLineLength: ...
path.close()

let shape = CAShapeLayer()
let shapeBorder = CAShapeLayer()
shapeBorder.strokeColor = UIColor.black.cgColor
shapeBorder.lineJoin = kCALineJoinRound
shapeBorder.lineCap = kCALineCapRound
shapeBorder.lineWidth = 10
shapeBorder.addSublayer(shape)

shape.path = path.cgPath
shapeBorder.path = shape.path
shape.lineJoin = kCALineJoinRound
shape.lineCap = kCALineCapRound
shape.lineWidth = shapeBorder.lineWidth-5.0
shape.strokeColor = color

But there is a shadow enter image description here


Solution

  • This works for me:

    extension UIBezierPath {
        func addArrow(start: CGPoint, end: CGPoint, pointerLineLength: CGFloat, arrowAngle: CGFloat) {
            self.move(to: start)
            self.addLine(to: end)
    
            let startEndAngle = atan((end.y - start.y) / (end.x - start.x)) + ((end.x - start.x) < 0 ? CGFloat(Double.pi) : 0)
            let arrowLine1 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle + arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle + arrowAngle))
            let arrowLine2 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle - arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle - arrowAngle))
    
            self.addLine(to: arrowLine1)
            self.move(to: end)
            self.addLine(to: arrowLine2)
        }
    }
    
    class MyViewController : UIViewController {
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            let arrow = UIBezierPath()
            arrow.addArrow(start: CGPoint(x: 200, y: 200), end: CGPoint(x: 50, y: 50), pointerLineLength: 30, arrowAngle: CGFloat(Double.pi / 4))
    
            let arrowLayer = CAShapeLayer()
            arrowLayer.strokeColor = UIColor.black.cgColor
            arrowLayer.lineWidth = 3
            arrowLayer.path = arrow.cgPath
            arrowLayer.fillColor = UIColor.clear.cgColor
            arrowLayer.lineJoin = kCALineJoinRound
            arrowLayer.lineCap = kCALineCapRound
            self.view.layer.addSublayer(arrowLayer)
        }
    }
    

    enter image description here

    EDIT

    Also, make sure you set your the fillColor of your CAShapeLayer to UIColor.clear.cgColor. Otherwise, your layer will fill in the area between the start point of the arrow and one of the lines at the end.