[macOS Swift] ビューに図形を描く３種類の方法

####macOS Mojava 10.14.6 / Xcode 11.3.1 / Swift 5.0
# (1) CALayerクラスを利用する
<img  src="http://mikomokaru.sakura.ne.jp/data/B58/v1.png" alt="[v1]" width="250">

図形を画面に貼り付けていくようなイメージである。図形も図形を貼り付ける画面もレイヤーオブジェクトの一つである。レイヤーは階層的に重ねていくことができる。

図形は、CALayerクラスから継承したCAShapeLayerオブジェクトとして作成し、描画は NSBezierPathクラスのメソッドを使う。作成した図形のレイヤーは addSublayerメソッドにより親ビューに付属するレイヤーに貼り付ける。サンプルコードでは、図形の貼り付け先はウィンドウのコンテントビューのレイヤーとしている。

なお、コード中の NSBezierPathクラスの cgPathメソッドは、NSBezierPath を CGPathに変換する極めて汎用的な処理にも関わらずフレームワークで提供されていないため、自前のExtensionとして実装している。コードは [stack overflow](https://stackoverflow.com/questions/1815568/how-can-i-convert-nsbezierpath-to-cgpath) を参考にした。

AppDelegate

```swift
func applicationDidFinishLaunching(_ aNotification: Notification) {
    self.window.contentView?.layer?.backgroundColor = NSColor.white.cgColor

    //円の作成
    let shape1 = CAShapeLayer()
    let path1 = NSBezierPath(ovalIn: NSRect(x: 10, y: 10,
                                            width: 100, height: 100))
    shape1.path = path1.cgPath
    shape1.fillColor = NSColor.cyan.cgColor
    shape1.lineWidth = 5
    shape1.strokeColor = NSColor.black.cgColor
    self.window.contentView?.layer?.addSublayer(shape1)
    
    //線の作成
    let shape2 = CAShapeLayer()
    let path2 = NSBezierPath()
    path2.move(to: NSPoint(x: 60, y: 60))
    path2.line(to: NSPoint(x: 260, y: 260))
    shape2.path = path2.cgPath
    shape2.lineWidth = 3
    shape2.strokeColor = NSColor.blue.cgColor
    self.window.contentView?.layer?.addSublayer(shape2)
}
```

## (2) drawメソッドをオーバーライドする <br> NSBezierPathクラスにより描画する

<img  src="http://mikomokaru.sakura.ne.jp/data/B58/v2.png" alt="[v2]" width="250">

NSViewクラスのサブクラスを作成し、drawメソッドの中で NSBezierPathクラスのメソッドにより図形を描画する。

```swift
class ViewBezier: NSView {

    override func draw(_ dirtyRect: NSRect) {      
        //円の描画
        let path1 = NSBezierPath(ovalIn: NSRect(x: 10, y: 10, width: 100, height: 100))
        NSColor.cyan.set()
        path1.fill()
        NSColor.black.set()
        path1.lineWidth = 5  //枠線
        path1.stroke()
        
        //線の描画
        NSColor.blue.set()
        let path2 = NSBezierPath.init()
        path2.lineWidth = 3
        path2.move(to: NSPoint(x: 60, y: 60))
        path2.line(to: NSPoint(x: 260, y: 260))
        path2.stroke()
    }   
}
```

AppDelegate

```swift
func applicationDidFinishLaunching(_ aNotification: Notification) {
    self.window.contentView?.layer?.backgroundColor = NSColor.white.cgColor
    //ビューの作成
    let view = ViewBezier.init(frame: self.window.contentView!.frame)
    self.window.contentView?.addSubview(view)
}
```

## (3) drawメソッドをオーバーライドする <br> Core Graphics機能により描画する

<img  src="http://mikomokaru.sakura.ne.jp/data/B58/v3.png" alt="[v3]" width="250">

NSViewクラスのサブクラスを作成し、drawメソッドの中でスクリーンの Graphic Contextを取得し、そこに CGContextクラスののメソッドを使用して図形を描画する。

```swift
class ViewContext: NSView {

    override func draw(_ dirtyRect: NSRect) {
        //円の描画
        let context:CGContext? = NSGraphicsContext.current?.cgContext
        context?.addEllipse(in: CGRect.init(x: 10, y: 10,
                                            width: 100, height: 100))
        context?.setFillColor(NSColor.cyan.cgColor)
        //context?.fillPath() //NG

        //枠線の描画
        context?.setLineWidth(5)
        context?.setStrokeColor(NSColor.black.cgColor)
        //context?.strokePath() //NG
        
        //コンテキストに円と枠線を一緒に出力する（NGのように別々に描画すると枠線が出力されない）
        context?.drawPath(using: .fillStroke)
        
        //線の描画
        context?.move(to: CGPoint(x: 60, y: 60))
        context?.addLine(to:CGPoint(x: 260, y: 260))
        context?.setStrokeColor(NSColor.blue.cgColor)
        context?.strokePath()    
    }
}
```

AppDelegate

```swift
func applicationDidFinishLaunching(_ aNotification: Notification) {
    self.window.contentView?.layer?.backgroundColor = NSColor.white.cgColor
    //ビューの作成
    let view = ViewContext.init(frame: self.window.contentView!.frame)
    self.window.contentView?.addSubview(view)
}
```

### [補足]

上記３種類のうち、いずれの方法をとるかはアプリケーションの性格次第であろう。
  パフォーマンスの良さは (3) > (2) > (1)
  機能の豊富さ、使い勝手の良さから言えば (1) > (2) > (3) であろう。

多数の図形を描画する場合には、(2)の応用として、一つの図形を一つのNSViewのサブクラスに描画し、それらを親ビューに貼り付けるという方法でも可能である。CALayerクラスを利用する方法でも描画自体は NSBezierPathクラスを利用しているので、結局どちらも同じでような手順を踏んでいる。CALayerクラスの採用は、つまるところ、図形の変形や回転、アニメーション、色のグラデーションなどといった多様な機能を利用するか否かに依る。