16

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.

Todd Ditchendorf
  • 11,217
  • 14
  • 69
  • 123
  • 1
    OP here. Immediately after posting this question I realized that `-[NSGraphicsContext CGContext]` is a 10.10 API. And as I stated, I am developing on 10.9 (even though my target is using the 10.10 Base SDK). So, that's the problem. This question is dumb, basically. This is literally the first line of Swift code I've written, so I'm still groping around, and I had just assumed I was doing something wrong syntactically. – Todd Ditchendorf Mar 19 '15 at 23:35

3 Answers3

27

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.
        }
    }
}
lgarbo
  • 750
  • 10
  • 12
9

Unfortunately, CGContext is only available in 10.10+. There’s also graphicsPort, but it has type UnsafeMutablePointer<Void> (and presumably must be wrangled somewhat to recover a viable CGContext), and it’s deprecated in 10.10.

If that doesn’t seem viable, you could create a bitmap context using CGBitmapContextCreate and draw into that; then you can retrieve an image from it and draw that to see the results.

Rob Rix
  • 358
  • 1
  • 8
  • 1
    Gotcha, thanks. It looks I really need to move to developing on 10.10 immediately to make things easier for myself. Gonna do that now. – Todd Ditchendorf Mar 19 '15 at 23:38
2

Eporedieses answer can be updated to

Swift 4

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

        return nil
    }
}
freytag
  • 4,769
  • 2
  • 27
  • 32