
Window title customization on Mac OS X
- Tutorial
Good afternoon,% username%!
Not so long ago, the need came to customize the window title of your program on Mac OS X. If iCal.app and Adress Book.app do this, then why not do the same?

The very first links from Google gave me some leads, and even one test program (after long dances with a tambourine) compiled and displayed its custom title. But it required the connection of private headers, their modifications (to match the new version of Mac OS X), etc. ... But I wanted the best, I wanted to make it easier, and even set the color of the window title text (to be in harmony with the new title color). Having discarded all the unsuccessful examples, I started digging leads ...
And I found out that in a regular program the undocumented class NSThemeFrame is responsible for rendering the window, we will work with him.
Caution! Under kat there is magic of runtime .
First, we need a private header NSThemeFrame.h (not original, but reversed, of course), it is easy to google it. If laziness, then here is a direct link . It is not necessary to add it to the project, we need it only for study.
Having glanced over it, we will pay attention to the drawRect: and _drawTitleStringIn: withColor: methods. The names are speaking, and we will overload them in order to fully control the rendering of the window. Armed, getting started.
First, we need to somehow get the NSThemeFrame class. You can get it from a private header, but this is a bad option. Suppose in AppDelegate we have an outlet for our NSWindow, then to get the desired class, do this:
Why? Because NSThemeFrame is the base View in the window, and our contentView is already located on it.
Secondly, we turn to magic .
We need to declare our class, in it the drawInRect: and _drawTitleStringIn: withColor: methods, then add these methods to the NSThemeFrame class (but under different names), and finally exchange the methods with the original ones in order to be able to call the original ones from the new ones.
Sounds complicated? Well, runtime to the rescue!
We declare a helper class DrawHelper (it will not be used directly, so we do not pay attention to warning when compiling).
Everything is quite simple here. We declare two colors - the title color and the text color, declare our class, it contains a bunch of methods that we need (we do not need to implement them, they are in the NSThemeFrame) and, in fact, our two methods for rendering text and background.
For simplicity of the example, I made a drawing of the standard heading and “colorized” it with one color (this allows a simple way to preserve the usual “bulk” heading). You can do a completely custom rendering using NSImage or gradients, and you don’t even have to call drawRectOriginal :, because then we won’t need a standard header. But we will leave it for independent exercises.
After calling the standard header rendering method, we proceed to create our drawing area. This is usually a rounded rectangle. I leave the implementation for other types of windows (for example, with non-rounded lower corners) for independent work.
Well, then comes the drawing of our color on top of the already rendered standard header in multiply mode (more about modes can be found in the documentation from Apple).
And at the very end, we draw our title text. Again, our function is called, which ignores the color passed to it and forcibly draws the text with a predetermined color (through the original drawing function).
And so we got to the most interesting! Actually, magic :
(in my case, I put this code in AppDelegate.m in order to be sure that the window will already be created)
In order:
1. get the NSThemeFrame class
2. take the drawRect method: from the DrawHelper class
3. add this method to the NSThemeFrame class under with the name drawRectOriginal:
4. we take from the NSThemeFrame class the drawInRect: and drawRectOriginal:
5. methods; we swap their implementation!
Next, we do the same for the _drawTitleStringIn: withColor: method.
And now we can rejoice! Our window pleases (or not) our eyes with its custom title color.
If you really want to do some kind of “skinning” (changing the color of the title on the fly), then the DrawHelper class and the contents of the applicationWillFinishLaunching function: you need to put it in a separate .m file, as well as declare and implement access functions to gFrameColor and gTitleColor. And do not forget to redraw all your windows after changing these settings. But this, again, will leave the reader as an independent work.
But, as one would expect, this approach has its drawbacks:
1. to get the NSThemeFrame class, we need an already created window;
2. this method does not imply separate customization of windows, for example, you cannot make two windows with different titles (of course, you can, but it will require a lot of effort and a lot of code);
3. windows can be drawn bypassing NSThemeFrame, for example, using NSGrayFrame, then this method most likely will not help, and you will have to play with the second class as well;
4. games with runtime are good in moderation.
PS: initially all this was done in the Qt + Cocoa bundle, but was ported to pure Cocoa. If anyone is interested in the tricks of Qt's interaction with Cocoa, then I can share the experience.
PPS: I don’t see the point of uploading the code to the github; it is very easily transferred to any project with a simple copy-paste in AppDelegate.m.
Not so long ago, the need came to customize the window title of your program on Mac OS X. If iCal.app and Adress Book.app do this, then why not do the same?

The very first links from Google gave me some leads, and even one test program (after long dances with a tambourine) compiled and displayed its custom title. But it required the connection of private headers, their modifications (to match the new version of Mac OS X), etc. ... But I wanted the best, I wanted to make it easier, and even set the color of the window title text (to be in harmony with the new title color). Having discarded all the unsuccessful examples, I started digging leads ...
And I found out that in a regular program the undocumented class NSThemeFrame is responsible for rendering the window, we will work with him.
Caution! Under kat there is magic of runtime .
First, we need a private header NSThemeFrame.h (not original, but reversed, of course), it is easy to google it. If laziness, then here is a direct link . It is not necessary to add it to the project, we need it only for study.
Having glanced over it, we will pay attention to the drawRect: and _drawTitleStringIn: withColor: methods. The names are speaking, and we will overload them in order to fully control the rendering of the window. Armed
First, we need to somehow get the NSThemeFrame class. You can get it from a private header, but this is a bad option. Suppose in AppDelegate we have an outlet for our NSWindow, then to get the desired class, do this:
id _class = [[[self.window contentView] superview] class];
Why? Because NSThemeFrame is the base View in the window, and our contentView is already located on it.
Secondly, we turn to magic .
We need to declare our class, in it the drawInRect: and _drawTitleStringIn: withColor: methods, then add these methods to the NSThemeFrame class (but under different names), and finally exchange the methods with the original ones in order to be able to call the original ones from the new ones.
Sounds complicated? Well, runtime to the rescue!
We declare a helper class DrawHelper (it will not be used directly, so we do not pay attention to warning when compiling).
#import
// global frame color
static NSColor * gFrameColor = nil;
// global title color
static NSColor * gTitleColor = nil;
@interface DrawHelper : NSObject
{
}
// to prevent errors
- (float)roundedCornerRadius;
- (void)drawRectOriginal:(NSRect)rect;
- (void) _drawTitleStringOriginalIn: (NSRect) rect withColor: (NSColor *) color;
- (NSWindow*)window;
- (id)_displayName;
- (NSRect)bounds;
- (void)_setTextShadow:(BOOL)on;
- (void)drawRect:(NSRect)rect;
- (void) _drawTitleStringIn: (NSRect) rect withColor: (NSColor *) color;
@end
@implementation DrawHelper
- (void)drawRect:(NSRect)rect
{
// Call original drawing method
[self drawRectOriginal:rect];
[self _setTextShadow:NO];
NSRect titleRect;
NSRect brect = [self bounds];
// creating round-rected bounding path
float radius = [self roundedCornerRadius];
NSBezierPath *path = [NSBezierPath alloc];
NSPoint topMid = NSMakePoint(NSMidX(brect), NSMaxY(brect));
NSPoint topLeft = NSMakePoint(NSMinX(brect), NSMaxY(brect));
NSPoint topRight = NSMakePoint(NSMaxX(brect), NSMaxY(brect));
NSPoint bottomRight = NSMakePoint(NSMaxX(brect), NSMinY(brect));
[path moveToPoint: topMid];
[path appendBezierPathWithArcFromPoint: topRight
toPoint: bottomRight
radius: radius];
[path appendBezierPathWithArcFromPoint: bottomRight
toPoint: brect.origin
radius: radius];
[path appendBezierPathWithArcFromPoint: brect.origin
toPoint: topLeft
radius: radius];
[path appendBezierPathWithArcFromPoint: topLeft
toPoint: topRight
radius: radius];
[path closePath];
[path addClip];
// rect for title
titleRect = NSMakeRect(0, 0, brect.size.width, brect.size.height);
// get current context
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
// multiply mode - for colorizing original border
CGContextSetBlendMode(context, kCGBlendModeMultiply);
// draw background
if (!gFrameColor)
// default bg color
gFrameColor = [NSColor colorWithCalibratedRed: (126 / 255.0) green: (161 / 255.0) blue: (177 / 255.0) alpha: 1.0];
[gFrameColor set];
[[NSBezierPath bezierPathWithRect:rect] fill];
// copy mode - for title
CGContextSetBlendMode(context, kCGBlendModeCopy);
// draw title text
[self _drawTitleStringIn: titleRect withColor: nil];
}
- (void)_drawTitleStringIn: (NSRect) rect withColor: (NSColor *) color
{
if (!gTitleColor)
// default text color
gTitleColor = [NSColor colorWithCalibratedRed: 1.0 green: 1.0 blue: 1.0 alpha: 1.0];
[self _drawTitleStringOriginalIn: rect withColor: gTitleColor];
}
@end
Everything is quite simple here. We declare two colors - the title color and the text color, declare our class, it contains a bunch of methods that we need (we do not need to implement them, they are in the NSThemeFrame) and, in fact, our two methods for rendering text and background.
For simplicity of the example, I made a drawing of the standard heading and “colorized” it with one color (this allows a simple way to preserve the usual “bulk” heading). You can do a completely custom rendering using NSImage or gradients, and you don’t even have to call drawRectOriginal :, because then we won’t need a standard header. But we will leave it for independent exercises.
After calling the standard header rendering method, we proceed to create our drawing area. This is usually a rounded rectangle. I leave the implementation for other types of windows (for example, with non-rounded lower corners) for independent work.
Well, then comes the drawing of our color on top of the already rendered standard header in multiply mode (more about modes can be found in the documentation from Apple).
And at the very end, we draw our title text. Again, our function is called, which ignores the color passed to it and forcibly draws the text with a predetermined color (through the original drawing function).
And so we got to the most interesting! Actually, magic :
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
id _class = [[[self.window contentView] superview] class];
// Exchange drawRect:
Method m0 = class_getInstanceMethod([DrawHelper class], @selector(drawRect:));
class_addMethod(_class, @selector(drawRectOriginal:), method_getImplementation(m0), method_getTypeEncoding(m0));
Method m1 = class_getInstanceMethod(_class, @selector(drawRect:));
Method m2 = class_getInstanceMethod(_class, @selector(drawRectOriginal:));
method_exchangeImplementations(m1, m2);
// Exchange _drawTitleStringIn:withColor:
Method m3 = class_getInstanceMethod([DrawHelper class], @selector(_drawTitleStringIn:withColor:));
class_addMethod(_class, @selector(_drawTitleStringOriginalIn:withColor:), method_getImplementation(m3), method_getTypeEncoding(m3));
Method m4 = class_getInstanceMethod(_class, @selector(_drawTitleStringIn:withColor:));
Method m5 = class_getInstanceMethod(_class, @selector(_drawTitleStringOriginalIn:withColor:));
method_exchangeImplementations(m4, m5);
}
(in my case, I put this code in AppDelegate.m in order to be sure that the window will already be created)
In order:
1. get the NSThemeFrame class
2. take the drawRect method: from the DrawHelper class
3. add this method to the NSThemeFrame class under with the name drawRectOriginal:
4. we take from the NSThemeFrame class the drawInRect: and drawRectOriginal:
5. methods; we swap their implementation!
Next, we do the same for the _drawTitleStringIn: withColor: method.
And now we can rejoice! Our window pleases (or not) our eyes with its custom title color.
If you really want to do some kind of “skinning” (changing the color of the title on the fly), then the DrawHelper class and the contents of the applicationWillFinishLaunching function: you need to put it in a separate .m file, as well as declare and implement access functions to gFrameColor and gTitleColor. And do not forget to redraw all your windows after changing these settings. But this, again, will leave the reader as an independent work.
But, as one would expect, this approach has its drawbacks:
1. to get the NSThemeFrame class, we need an already created window;
2. this method does not imply separate customization of windows, for example, you cannot make two windows with different titles (of course, you can, but it will require a lot of effort and a lot of code);
3. windows can be drawn bypassing NSThemeFrame, for example, using NSGrayFrame, then this method most likely will not help, and you will have to play with the second class as well;
4. games with runtime are good in moderation.
PS: initially all this was done in the Qt + Cocoa bundle, but was ported to pure Cocoa. If anyone is interested in the tricks of Qt's interaction with Cocoa, then I can share the experience.
PPS: I don’t see the point of uploading the code to the github; it is very easily transferred to any project with a simple copy-paste in AppDelegate.m.