I have a custom NSView 'MyView' that displays an NSImage that is expensive to create. Ideally, this rendering should happen on a background threat and MyView should update itself when rendering is done.
To achieve this, I followed the suggestion in WWDC 2013, Session 215 (around 4:00).
It works like this: When drawRect is called and the image wasn't created yet, rendering is triggered on a background queue. There, the image is created, stored in an instance variable and setNeedsDisplay is called again (on the main thread) to mark the view as dirty. That will call drawRect a second time where the image is now present and can be drawn:
- (void)drawRect:(NSRect)dirtyRect
{
// Do we have an image?
if( self.image )
{
// Yes, we can draw the image (and invalidate it right away for demo purposes)
[self.image drawInRect:self.bounds];
self.image = nil;
}
else
{
// No, we have to async render the image first and mark the view as dirty afterwards
CGSize imageSize = self.bounds.size;
dispatch_async( dispatch_get_global_queue( QOS_CLASS_USER_INTERACTIVE, 0 ), ^
{
self.image = [self _renderImageWithSize:imageSize];
dispatch_async( dispatch_get_main_queue(), ^
{
[self setNeedsDisplayInRect:dirtyRect];
});
});
}
}
- (NSImage *)_renderImageWithSize:(NSSize)size
{
// Simulate expensive image rendering (just for demo purposes)
NSBitmapImageRep * bitmapRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil pixelsWide:size.width pixelsHigh:size.height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:0 bitsPerPixel:32];
NSGraphicsContext * context = [NSGraphicsContext graphicsContextWithBitmapImageRep:bitmapRep];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:context];
// Draw oval with random hue.
float hue = ( (float)( labs( random() % 100 )) / 100.0 );
[[NSColor colorWithHue:hue saturation:0.5 brightness:1.0 alpha:1.0] setFill];
[[NSBezierPath bezierPathWithOvalInRect:NSMakeRect( 0.0, 0.0, size.width, size.height )] fill];
[NSGraphicsContext restoreGraphicsState];
NSImage * image = [[NSImage alloc] init];
[image addRepresentation:bitmapRep];
// Simulate super-expensive rendering
sleep( 1 );
return image;
}
That code works fine but it creates an annoying flicker. It seems that the view is cleared in the first call to drawRect. It stays cleared until the second drawRect actually draws the rendered image.
Of course, I could just draw the old image, but I would prefer not to draw stale data unnecessarily.
Is there a way to keep the view from clearing in drawRect?