macOS Mojava 10.14.6 / Xcode 11.3.1 / Swift 5.0
(1) CALayerクラスを利用する
![[v1]](https://qiita-user-contents.imgix.net/http%3A%2F%2Fmikomokaru.sakura.ne.jp%2Fdata%2FB58%2Fv1.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=ac21015515bee1b073b67454357373ed)
図形を画面に貼り付けていくようなイメージである。図形も図形を貼り付ける画面もレイヤーオブジェクトの一つである。レイヤーは階層的に重ねていくことができる。
図形は、CALayerクラスから継承したCAShapeLayerオブジェクトとして作成し、描画は NSBezierPathクラスのメソッドを使う。作成した図形のレイヤーは addSublayerメソッドにより親ビューに付属するレイヤーに貼り付ける。サンプルコードでは、図形の貼り付け先はウィンドウのコンテントビューのレイヤーとしている。
なお、コード中の NSBezierPathクラスの cgPathメソッドは、NSBezierPath を CGPathに変換する極めて汎用的な処理にも関わらずフレームワークで提供されていないため、自前のExtensionとして実装している。コードは stack overflow を参考にした。
AppDelegate
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メソッドをオーバーライドする
NSBezierPathクラスにより描画する
![[v2]](https://qiita-user-contents.imgix.net/http%3A%2F%2Fmikomokaru.sakura.ne.jp%2Fdata%2FB58%2Fv2.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=8dee1f0bf4779d1b028242ab30ac5f69)
NSViewクラスのサブクラスを作成し、drawメソッドの中で NSBezierPathクラスのメソッドにより図形を描画する。
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
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メソッドをオーバーライドする
Core Graphics機能により描画する
![[v3]](https://qiita-user-contents.imgix.net/http%3A%2F%2Fmikomokaru.sakura.ne.jp%2Fdata%2FB58%2Fv3.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=45e4f260e91eecca0d939cc08ffff101)
NSViewクラスのサブクラスを作成し、drawメソッドの中でスクリーンの Graphic Contextを取得し、そこに CGContextクラスののメソッドを使用して図形を描画する。
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
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種類のうち、いずれの方法をとるかはアプリケーションの性格次第であろう。
パフォーマンスの良さは (3) > (2) > (1)
機能の豊富さ、使い勝手の良さから言えば (1) > (2) > (3) であろう。
多数の図形を描画する場合には、(2)の応用として、一つの図形を一つのNSViewのサブクラスに描画し、それらを親ビューに貼り付けるという方法でも可能である。CALayerクラスを利用する方法でも描画自体は NSBezierPathクラスを利用しているので、結局どちらも同じでような手順を踏んでいる。CALayerクラスの採用は、つまるところ、図形の変形や回転、アニメーション、色のグラデーションなどといった多様な機能を利用するか否かに依る。