Mac OS X: custom background for your app-window

I found this neat little technique: http://parmanoir.com/Custom_NSThemeFrame … for drawing a custom image to the background of your Application Window.

Here’s a screenshot of the technique applied to Regular Expression Helper (currently in submission, waiting for Apple to approve it for the App Store):

Things of note:

  1. The author was inspired by Apple’s own apps – recently, Apple has been doing this more and more, even though I’m sure it’s against their own Human Interface Guidelines … so I’m assuming they’ll have no complaints about you doing this in the App Store
  2. This covers your ENTIRE background – it overwrites the default titlebar, overwrites the default bottom bar, everything
  3. The author uses method swizzling to make this work *without* creating custom classes, and *without* altering Apple’s own system classes. That’s very neat: you can comment out a single line of code to enable/disable the customization

For my last OS X app – Regular Expressions Helper – Brett made an excellent icon. As required by Apple’s App Store policies, we’ve got it in all sizes up to 512×512. So, I wanted to use that as the background to the app. This needed some tweaks.

Also, the Parmanoir site skipped over some minor points (all obvious when you read the source code they provided), but for future reference, here’s my changes:

Code requirements

You need to import objc/runtime.h for method swizzling to work – a little surprising, considering you’d expect this to be part of the base stuff pre-imported by NSObject et al. No matter.

Complication: Xcode4 auto-complete is buggy on the objc directory – it displays it as a file, and attempts to autocomplete:

#import  // FAIL...

So, just remember to type it manually (or copy/paste this):

#import  // Correct

Loading your application icons (.icns file) as a background image

If you load an NSImage directly, using the standard method from iOS:

	NSImage* backgroundImage = [NSImage imageNamed:@"image-name"]; // DONT DO THIS

…then you’ll discover a slightly irritating feature of OS X v10.6: it loads a 32×32 pixel version of the image *no matter how big the image is*. This is well documented, but it’s easy to overlook, especially coming from iOS, where “imageNamed” is the method you *always* use, to take advantage of it’s built-in caching.

Instead, if you simply use a different init method, you get the “maximal resolution” version of the image:

	NSImage* backgroundImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Regular Expressions Icons" ofType:@"icns" ]];

(note: this is loading an .icns file – the one that Apple requires you to create, and requires you to put a 512×512 image into – rather than, say, a PNG file. That makes things easier to maintain: just one image file to update ;))

Copy/pasteable final custom drawRect method

For sheer convenience, here’s the default basic method for doing background-image rendering from icns, with the alpha scaled down so you can see the image more clearly:

-(void) customDrawRect:(NSRect) rect
{
	// Call original drawing method
	[self drawRectOriginal:rect];
		
	//
	// Build clipping path : intersection of frame clip (bezier path with rounded corners) and rect argument
	//
	NSRect windowRect = [[self window] frame];
	windowRect.origin = NSMakePoint(0, 0);
	
	//
	// Draw background image (extend drawing rect : biggest rect dimension become's rect size)
	//
	NSRect imageRect = windowRect;
	if (imageRect.size.width > imageRect.size.height)
	{
		imageRect.origin.y = -(imageRect.size.width-imageRect.size.height)/2;
		imageRect.size.height = imageRect.size.width;
	}
	else
	{
		imageRect.origin.x = -(imageRect.size.height-imageRect.size.width)/2;
		imageRect.size.width = imageRect.size.height;
	}
	
	NSImage* backgroundImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Regular Expressions Icons" ofType:@"icns" ]];
	
	[backgroundImage drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositeSourceAtop fraction:0.55];
}

Leave a Reply

Your email address will not be published. Required fields are marked *