Swift on OS X: How to access the current CGContext for drawing in -[NSView drawRect:]

Todd Ditchendorf picture Todd Ditchendorf · Mar 20, 2015 · Viewed 15.7k times · Source

Setup:

Xcode 6.1
OS X 10.9.5
Swift Mac Document-based Application target

Question:

I am using Swift to make a Cocoa Mac app (NOT iOS!). I have a custom NSView subclass. I'd like to override -drawRect:, and access the current CGContext to do some drawing using the CoreGraphics APIs.

However, I can't seem to access the current CGContext in Swift (something I've done hundreds of times in ObjC). Here's my code:

import Cocoa

class Canvas: NSView {
    override func drawRect(dirtyRect: CGRect) {
        if let ctx: CGContext! = NSGraphicsContext.currentContext()?.CGContext {
            // do drawing
        }
    }
}

When I run, I get an immediate crash on the first line of my drawRect implementation:

-[NSWindowGraphicsContext CGContext]: unrecognized selector sent to instance 0x60800005e840

What am I doing wrong? How do I fetch the current CGContext for drawing in Swift?

NOTE:

I definitely want to use the CoreGraphics API. Unless using the CoreGraphics API in Swift is entirely impossible, please do not respond with suggestions on how to use AppKit drawing APIs instead (I don't care if they are easier). I want to know how to use CoreGraphics from Swift on OS X.

Answer

Eporediese picture Eporediese · Jul 22, 2015

Example

Swift 3+

class CustomView: NSView {

    private var currentContext : CGContext? {
        get {
            if #available(OSX 10.10, *) {
                return NSGraphicsContext.current?.CGContext
            } else if let contextPointer = NSGraphicsContext.currentContext()?.graphicsPort {
                let context: CGContextRef = Unmanaged.fromOpaque(COpaquePointer(contextPointer)).takeUnretainedValue()
                return context
            }

            return nil
        }
    }

    private func saveGState(drawStuff: (ctx:CGContextRef) -> ()) -> () {
        if let context = self.currentContext {
            CGContextSaveGState (context)
            drawStuff(ctx: context)
            CGContextRestoreGState (context)
        }
    }

    override func drawRect(dirtyRect: NSRect) {
        super.drawRect(dirtyRect)

        saveGState { ctx in
            // Drawing code here.
        }
    }
}

Swift 2

class CustomView: NSView {

    private var currentContext : CGContext? {
        get {
            if #available(OSX 10.10, *) {
                return NSGraphicsContext.currentContext()?.CGContext
            } else if let contextPointer = NSGraphicsContext.currentContext()?.graphicsPort {
                let context: CGContextRef = Unmanaged.fromOpaque(COpaquePointer(contextPointer)).takeUnretainedValue()
                return context
            }

            return nil
        }
    }

    private func saveGState(drawStuff: (ctx:CGContextRef) -> ()) -> () {
        if let context = self.currentContext {
            CGContextSaveGState (context)
            drawStuff(ctx: context)
            CGContextRestoreGState (context)
        }
    }

    override func drawRect(dirtyRect: NSRect) {
        super.drawRect(dirtyRect)

        saveGState { ctx in
            // Drawing code here.
        }
    }
}