【Swift4】矢印を描画する

UIViewController上に矢印を描画したい。

幾つかの方法があるようだが、今回はUIBezierPathによる描画を試みた。

参考にしたのは以下のサイト:
qiita.com
i-app-tec.com


では本題。
構想としては、画面上にUIView()を生成し、その中に始点と終点を指定して矢印を描画していく。
動的に矢印を増やしたいため、1つのUIView()につき1つの矢印を描画する(静的な場合は1つのUIView()に複数の矢印を描画することも可能です)。

まず、UIViewのサブクラスとしてArrowViewクラスを作ります。
⌘Nで新しいファイルを作成 -> [Swift File] を選択。タイトルはArrowViewとします。

import UIKit

class ArrowView: UIView {
    var fromPoint: CGPoint!
    var toPoint: CGPoint!
    
    init(frame: CGRect, from: CGPoint, to: CGPoint) {
        super.init(frame: frame)
        fromPoint = from
        toPoint = to
        self.isOpaque = false
        self.isUserInteractionEnabled = false
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
        
    override func draw(_ rect: CGRect) {
        let arrow = UIBezierPath.arrow
                                ( from: fromPoint,
                                  to: toPoint,
                                  tailWidth: 0.5,
                                  headWidth: 8,
                                  headLength: 8 )
        UIColor.black.setStroke()
        UIColor.black.setFill()
        arrow.lineWidth = 0.9
        arrow.stroke()
    }
}

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)
    }
}

次に、表示したいView上で以下のコードでArrowViewを生成し、addSubViewします。
今回は ViewController: UIViewController 上に生成してみます。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        //ArrowViewを生成
        let arrow1 = ArrowView(frame: view.frame, from: CGPoint(x:100, y:100), to: CGPoint(x:10, y:10))
        self.view.addSubview(arrow1)
    }
}

これで矢印が描画できると思います。
各種プロパティは用途に応じて適宜変更してください。

isOpaque = false //背景色を黒にしない(デフォルトはtrue: 黒)
isUserInteractionEnabled = false // タップを無効化する
UIColor.black.setStroke() // 矢印の色を設定
UIColor.black.setFill() // 中抜きにしない
arrow.lineWidth = 0.9 // 線の太さを設定