트랙킹 캔버스 뷰 만들기 (Cocoa)

예전에 코어 그래픽을 사용해서 UIView위에 손가락으로 그림을 그릴 수 있는 간단한 핑거 드로잉 캔버스를 구현해본 바 있는데, 똑같은 내용을 NSView에 적용해보고자 한다. 이전글이 Objective-C로 작성되어 있는데, 이번에는 Swift로 간단하게 작성해보려 한다. 원리는 동일하다. CGLayer를 하나 만들고, 마우스를 사용해서 뷰를 긁을 때(드래그할 때)마다  코어 그래픽을 사용해서 레이어에 그림을 그리고, 다시 뷰 리드로잉 사이클에서는 뷰에 레이어를 그리는 것이다.

class TrackDrawCanvasView: NSView {
  var previousPoint: NSPoint? = nil
  lazy var drawingLayer: CGLayer? = { [unowned self] in
    let cs = CGColorSpace(name:CGColorSpace.sRGB)!
    let ctx = CGContext(data: nil,
                        width: 100,
                        height: 100,
                        bitsPerComponent: 8,
                        bytesPerRow: 0,
                        space: cs,
                        BitmapInfo: CGImageAlphaInfo.nonSkipLast.rawValue)
   if let ctx = ctx {
     let layer = CGLayer(ctx, size: self.bounds.size, auxiliaryInfo: nil)
     return layer
   }
   return nil
  }()

  lazy var drawingContext: CGContext? = { [unowned self] in
    let ctx = self.drawingLayer?.context
    // 그래픽 컨텍스트 셋업
    ctx?.setStrokeColor(NSColor.green.cgColor)
    ctx?.setLineWidth(3.0)
    // 이벤트 위치를 프레임만큼 보정 
    ctx?.translateBy(x:-self.frame.origin.x, y:-self.frame.origin.y)
    return ctx
  }
}

터치할 때 마우스 위치와 선이 그려지는 위치를 맞추기 위해서 좌표계를 뷰의 위치만큼 거꾸로 이동시켰다. 다음은 화면을 그릴 차례이다. NSEvent는 드래그에 대해서 이전 위치를 가지고 있지 않기 때문에 이전 위치를 추적해 나가야 한다.

///
var previousPoint: NSPoint? = nil

override func mouseDown(wit틀h event: NSEvent) {
    previousPoint = event.locationInWindow
}

override func mouseDown(with event: NSEvent) {
    previousPoint = nil
}

override func mouseDragged(with event: NSEvent) {
    let currentPoint = event.locationInWindow
    drawingContext?.beginPath()
    drawingContext?.move(to: previousPoint)
    drawingContext?.addLine(to: currentPoint)
    drawingContext?.strokePath()
    previousPoint = currentPoint
    needsDisplay = true
}

최종적으로 뷰를 그릴 때는 레이어를 그려주면 된다.

override func draw(_ dirtyRect: NSRect) {
  super.draw(dirtyRect)
  if let ctx = NSGraphicsContext.current?.cgContext,
  let layer = drawingLayer {
    ctx.draw(layer, at: CGPoint.zero)
  }
}

인터페이스 빌더에서 뷰를 윈도에 하나 올린 후 드래실행해보자. 끝!

참고

 

NSGraphicsContext로부터 CGContextRef 얻기

아직 문서화가 안됐는데, NSGraphicsContext 객체로부터 CGContextRef 객체를 얻기 위해서 이전에는 -graphicsPort를 이용했는데, 이 프로퍼티는 deprecated되었고, (10.9 이상) 현재는 CGContext 프로퍼티를 쓴다. 헤더에만 나와있으니 참고