Episode #293

Working in AppKit

Series: Dive Into Core Graphics

8 minutes
Published on July 21, 2017

This video is only available to subscribers. Get access to this video and 582 others.

Core Graphics is a cross-platform technology, but there are some gotchas to consider when working on mac apps with AppKit. Sam will go over some of these differences to help you avoid some common pitfalls.

Preparing the Cocoa Playground

import AppKit  // or Cocoa
import PlaygroundSupport

final class CustomView: NSView {
}
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 320, height: 320))
PlaygroundPage.current.liveView = view

Getting Layer-backed NSViews

By default NSView is not backed by a layer. To change this:

view.wantsLayer = true
view.layer

Flipping the Drawing

NSView by default draws upside down compared to UIView. To change this you can invert the y-axis like we’ve seen before, or we can override isFlipped:

override var isFlipped: Bool {
    return true
}

Drawing in an NSView

override func draw(_ dirtyRect: NSRect) {
    NSColor.white.setFill()
    context.fill(bounds)

    NSColor.blue.setFill()
    context.fill(CGRect(x: 20, y: 20, width: 150, height: 150))

    let text = "Hello" as NSString
    text.draw(at: CGPoint(x: 20, y: 190), withAttributes: [:])
}

Differences here are using NSColor over UIColor.

Drawing into an NSImage

Drawing into an NSImage is a bit different than UIImage as well. We’ll create a new instance of NSImage and pass a block to it that we can use to do our drawing. To get the context, we call NSGraphicsContext.current() and it returns an object that we can use to get the cgContext from.

let image = NSImage(size: CGSize(width: 320, height: 320), flipped: true) { bounds in
    guard let context = NSGraphicsContext.current()?.cgContext else { return false }

    NSColor.white.setFill()
    context.fill(bounds)

    NSColor.blue.setFill()
    context.fill(CGRect(x: 20, y: 20, width: 150, height: 150))

    let text = "Hello" as NSString
    text.draw(at: CGPoint(x: 20, y: 190), withAttributes: [:])

    return true
}
image

This episode uses Swift 3.0, Xcode 8.3.