Parmanoir

Custom NSThemeFrame

EDIT to completely change the window appearance, check out Matt Gallagher's Drawing a custom window

After seeing Safari 4 drawing its tab bar in a custom way, I wondered how to do that. Each window has a frame view (the superview of contentView) that draws the window, replacing that view's drawRect: with our own will let us draw it ourselves !

Image:Custom NSThemeFrame drawing.png

First, replace the window's frame drawRect: with our own :

	// Get window's frame view class
	id class = [[[window contentView] superview] class];

	// Add our drawRect: to the frame class
	Method m0 = class_getInstanceMethod([self class], @selector(drawRect:));
	class_addMethod(class, @selector(drawRectOriginal:), method_getImplementation(m0), method_getTypeEncoding(m0));
	
	// Exchange methods
	Method m1 = class_getInstanceMethod(class, @selector(drawRect:));
	Method m2 = class_getInstanceMethod(class, @selector(drawRectOriginal:));
	method_exchangeImplementations(m1, m2);

Cocoa will then call our method each time the window frame needs to be drawn. We can draw an entirely custom frame or draw over the existing one. (The attached sample does the latter)

A standard Cocoa window has rounded corners that need to be accounted for by building a clipping path to clip our custom drawing to the window shape :

  • Get the rounded corner radius with roundedCornerRadius
  • Using this radius, build a rounded corner path that describes the window
  • Intersect that path with the current rect
  • Draw our stuff
- (void)drawRect:(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);

	float cornerRadius = [self roundedCornerRadius];
	[[NSBezierPath bezierPathWithRoundedRect:windowRect xRadius:cornerRadius yRadius:cornerRadius] addClip];
	[[NSBezierPath bezierPathWithRect:rect] addClip];

	// Any custom drawing goes here
	...
}
Sample Code Image:iconZip.png Custom frame drawing.zip draws an NSImageNameActionTemplate image and a custom color (in kCGBlendModeColorDodge) over the existing frame

Karsten
2009 03 15

can't you just replace the themeview with your own object? i mean, the NSWindow somehow sets this object, so i'm sure you can simply replace it. i guess you don't need to method swizzle NSThemeFrame, but instead create your subclass of NSThemeFrame and use that instead. I've just had a look. there's a _borderView instvar in NSWindow which keeps this theme frame so it could work if you change it, but i'm sure you'd need to do some housekeeping as well. like setting up the view in the window properly and such stuff...

Karsten

Patrick Geiller
2009 03 15

I guess it's possible. I wanted to draw over the existing frame, letting it handle drawing of buttons, toolbar, resize container, etc. Replacing the frame would mean I'd have to do it, and that's too much work ;)

William Dahlberg
2009 03 15

Cool tip! I was going to look into doing something like this for the app I'm working on to experiment with giving it a bit of a darker look. Not sure if it'll look good or just weird yet though, it's a fine line between giving an app a unique style and just messing with things that are already good. This technique seems easy enough to quickly find out though.

Thanks for sharing a way of doing it!

Patrick Geiller
2009 03 15

You're welcome !

I just tried darkening the window, it quickly looks very dark. You might have to draw the title and the toolbar labels yourself.

Jens
2009 08 14

Great snippet!

I'm searching for a way to make the two bottom corners of a textured window have 'sharp' or 'square' corner like Safari, as I want a status bar down there. I once found out around 7 years ago, but I lost that snippet.

My solution was done when the window was initialized somehow, it didn't use more than 1 or 2 lines, but as I recall it, it didn't seem clear that it should/could be done this way.

I believe that I made a window in IB, then when loaded, I changed it from non-textured to textured, while I also changed some styling or something similar.

Do you by any chance know how to do this?

Patrick Geiller
2009 08 14

Jens, you're looking for NSWindow's setBottomCornerRounded.

http://parmanoir.com/NSWindow_goodies_:_bottomCornerRounded,_usesLightBottomGradient
Jens
2009 08 14

Patrick, you are a genious! :)

It's exactly what I've been looking for. -And I would probably not have found it without your help, as it seems it's a private API - it gives me a compiler warning that NSWindow may not respond to it, but I just checked that it respondsToSelector:@selector(setBottomCornerRounded) and I got a YES. =)

It's probably also far better than my 6 year old solution, thankyou a billion!

Patrick Geiller
2009 08 14

You're welcome ! That's the sort of thing you can stumble upon when playing around with F-Script. It will let you explore all Cocoa classes and methods at runtime.

http://www.fscript.org/

Aaron Wallis
2011 06 13

Any idea if this kind of hack is accepted in the App Store?

Nate
2014 01 31

Can you update this to work with Xcode 5? For some reason it won't let me build against the SDK.


Follow me on Twitter
Planet Cocoa
Cocoa.fr

2011 02 22Distance field
2010 07 202Binding through NSApp
2010 05 122Forwarding invocations
2010 02 272Core Image black fringes
2010 02 21Quickest Way to Shell
2010 02 08Who's calling ?
2009 09 2138 ways to use Blocks in Snow Leopard
2009 08 182Bracket Mess
2009 08 124Taming JavascriptCore within and without WebView
2009 04 15Debugging with Activity Monitor
2009 03 25How Core Image Color Tracking works
2009 03 1510Custom NSThemeFrame
2009 03 10Which framework is running ?
2009 03 074CoreUI can paint pretty big
2009 02 18Localization with functions
2009 01 30Did you forget to nest alloc and init?
2009 01 16JSCocoa on the iPhone
2009 01 11Mixing WebView and JavascriptCore
2009 01 09Badge overflow
2009 01 09Find your Garbage Collection leaks with Instruments

Powered by MediaWiki