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?
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).
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With