Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing color of NSWindow title text

Tags:

cocoa

nswindow

I know this won't be a popular question and some people don't like apps that have a non-standard look, but it is useful for my application.

Is it possible to change the color of the NSWindow's titlebar text, in a "standard" non-private API way?

I know it's possible if I use private API (as mentioned in this answer) but I presume it is possible to do it without private API as Pixelmator has done it and not been rejected from MAS. I know it's also possible to do it by making a borderless window and drawing everything myself, but I don't think that's how Pixelmator is doing it, because they still get all the additional bits that comes with the standard NSWindow titlebar; draggable icons, rename the window, the dropdown menu for document revisions and the fullscreen button.

Basically, I've made a black window using setBackgroundColor: but the text still comes up as black, which doesn't work on a black background.

So does anyone know a way to do this, or how Pixelmator is doing it?

like image 324
iain Avatar asked Sep 07 '12 17:09

iain


3 Answers

This is the way I do It:

#import <objc/runtime.h>

@interface SOWindow : NSWindow
@end

@interface SOWindowFrameOverrides : NSView
@end

@implementation SOWindow

+ (void)load
{
    SEL selector = NSSelectorFromString(@"_currentTitleColor");
    SEL originalSelector = NSSelectorFromString(@"_original_currentTitleColor");
    Class frameClass = NSClassFromString(@"NSThemeFrame");

    Method m = class_getInstanceMethod(frameClass, selector);
    Method m2 = class_getInstanceMethod([SOWindowFrameOverrides class], selector);
    class_addMethod(frameClass, originalSelector, method_getImplementation(m), method_getTypeEncoding(m));
    method_exchangeImplementations(m, m2);
}

@end

@implementation SOWindowFrameOverrides

- (NSColor *)_currentTitleColor
{
    if ([self.window isKindOfClass:[SOWindow class]]) {
        return [NSColor redColor];
    } else {
        return [self _original_currentTitleColor];
    }
}

- (NSColor *)_original_currentTitleColor
{
    // will be filled in at runtime
    return nil;
}

@end

While the view hierarchy has changed a lot from Mavericks to Yosemite, _currentTitleColor has been in the API for a long time and will probably not change in the near future. Even tough method swizzling is a hacky way of doing It, I find It more elegant than traversing the view hierarchy, but that's just me.

If you want to customize the title even further, you can override _titleTextField on NSThemeFrame to return a customized text field (I have used It to change the backgroundStyle on my F3X project).

like image 30
Guilherme Rambo Avatar answered Oct 19 '22 02:10

Guilherme Rambo


Here is a solution in Swift. It is late and I'm tired so this is probably not optimal but it works.

First, here is a function to find a view within an hierarchy, with the option to skip a specific view. (Which is useful if we are going search through window.contentView.superview.subviews and we want to ignore your own views in the contentView)

func findViewInSubview(subviews: [NSView], #ignoreView: NSView, test: (NSView) -> Bool) -> NSView? {
    for v in subviews {
        if test(v) {
            return v
        } else if v != ignoreView {
            if let found = findViewInSubview(v.subviews as [NSView], ignoreView: ignoreView, test) {
                return found
            }
        }
    }
    return nil
}

And here is how you would use it, for example from an NSViewController subclass. Note that you need to do it when the window has become visible, so you can't do it in viewDidLoad.

override func viewDidAppear() {
    if let windowContentView = view.window?.contentView as? NSView {
        if let windowContentSuperView = windowContentView.superview {
            let titleView = findViewInSubview(windowContentSuperView.subviews as [NSView], ignoreView: windowContentView) { (view) -> Bool in
                // We find the title by looking for an NSTextField. You may
                // want to make this test more strict and for example also
                // check for the title string value to be sure.
                return view is NSTextField
            }
            if let titleView = titleView as? NSTextField {
                titleView.attributedStringValue = NSAttributedString(string: "Hello", attributes: [NSForegroundColorAttributeName: NSColor.redColor()])
            }
        }
    }
}

Do note that you are playing with fire. Internals like this are unspecified for a reason.

like image 57
Stefan Arentz Avatar answered Oct 19 '22 04:10

Stefan Arentz


Since OS X 10.10 it should be enough to change the appearance of the window to NSAppearanceNameVibrantDark

  window.appearance = NSAppearance(named:NSAppearanceNameVibrantDark)

Thought it's worth to mention since most of the answers you can find are out of date.

like image 11
Marc T. Avatar answered Oct 19 '22 04:10

Marc T.