Drawing off-screen in Cocoa

By uliwitness

In general, drawing in Cocoa always happens in an NSView‘s -drawRect: method on the main thread. The OS calls that whenever you’re supposed to draw anything, and if you want to redraw, you use -setNeedsDisplay: to ask the OS to call you back, and it will in turn call -drawRect:.

But sometimes, you just want to draw on a secondary thread, or you want to draw something for later use that shouldn’t show up in the view right now. The approach mentioned above seems awfully limited, doesn’t it? Of course there are other ways to do that.

A very easy way is to just take an NSImage, create it in your helper thread or threaded NSOperation, call -lockFocus on it, draw your stuff, call -unlockFocus and then send the finished image back off to your main thread using -performSelectorOnMainThread:withObject:waitUntilDone:. Note though, that doing a -lockFocus on an NSImage pretty much deletes and recreates the image contents, along with a Graphics Context needed to draw into the image, so if you plan to lockFocus/unlockFocus a lot, this will be fairly slow. In this case, you might be better off creating an NSBitmapImageRep directly and drawing into that, or even creating a CGBitmapGraphicsContext, doing your drawing in there, and only creating an NSImage from that once you’re finished.

Since you decide what to do with the NSImage once you’ve created it, this is a decent way to cache drawing that will need to be displayed later, or to pre-render more detailed versions of tiles of an image in the background to allow for more detail when zooming. It is also a way to speed up drawing of complex stuff. The CPU and GPU are fast enough to blast a full-screen pixel buffer to the screen at 50fps or more, but actually doing anti-aliased rendering of bezier paths or laying out text at this rate might be taxing the machine a bit. It also means all your drawing resources are together on your secondary thread, completely disconnected from the main thread. So it’s unlikely your thread has to wait for someone on the main thread to finish drawing, unnecessarily slowing things down.

If you really, urgently need to draw now, and need to do it on another thread (e.g. for video playback), you’ll need to do the threaded NSView drawing thing: Call -lockFocusIfCanDraw on the view, do your drawing, being careful that all your state has thread-safety locks or is immutable, call -flushGraphics on the current context and then -unlockFocus again. This is a bit dangerous due to all the thread synchronization points, though, and doesn’t work all the way back across system releases, so don’t do that if you can avoid it. If you want to learn more, see Apple’s thread-safety documentation.

1 Comment Leave a comment

  1. Thanks for posting this.
    I have created a cross platform C++ library and wish to have an offscreen text drawing for OSX and Windows.

Share your thoughts