Change NSWindow Header Text Color

I know that this will not be a popular issue, and some people do not like applications that have a non-standard look, but it is useful for my application.

Is it possible to change the text color of the NSWindow header in a standard API that is not private?

I know that this is possible if I use a private API ( as mentioned in this answer) , but I believe that it can be done without a private API, since Pixelmator did this and was not rejected from MAS. I also know that this can be done by making a limitless window and drawing everything myself, but I don’t think that Pixelmator does it because they still receive all the extra bits that come with the standard NSWindow header; draggable icons, window renaming, a drop-down menu for editing a document and a full-screen button.

Basically, I made a black window using setBackgroundColor: but the text still looks black, which does not work on a black background.

Does anyone know a way to do this or how does the Pixelmator do it?

+8
cocoa nswindow
source share
4 answers

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

window.appearance = NSAppearance(named:NSAppearanceNameVibrantDark) 

The thought is worth mentioning, since most of the answers you can find are outdated.

+8
source share

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

First, here is the function to find the view within the hierarchy, with the ability to skip a specific view. (Which is useful if we 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 use it, for example, from a subclass of NSViewController . Note that you need to do this when the window has become visible, so you cannot do this 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()]) } } } } 

Remember that you play with fire. Internal ones like this are not indicated for any reason.

+1
source share

You can solve this, as indicated in the comments on your question, using some private API - you, however, cannot send this application to the AppStore.

Another solution is to get [[myWindow contentView] superview] - which will bring you the above instance of NSThemeView . All you have to do is look in the sub-items of the view (actually called the frame view) for any instances of NSTextField and change them. Note that the hierarchy of these private views may change with each version of OS X, potentially dropping your code.

Most likely, the best solution is to subclass (only a subclass, no other setting) NSWindow and implement the -title and -setTitle: - for each window you must set the actual title @"" (by calling [super setTitle:@""] ), and then put your own, programmatically created NSTextField instance in the frame view (ie [[[self contentView] superview] addSubview:myTextField] , where myTextField is a text field). You need to find out the exact placement, etc. field, but this is the easiest part.

0
source share

Here is how 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 presentation hierarchy has changed a lot from Mavericks to Yosemite, _currentTitleColor has been used in the API for a long time and probably won't change in the near future. Even the hard swizzling method is a hacker way to do this, I find it more elegant than moving the hierarchy of views, but that's just me.

If you want to further customize the title, you can override _titleTextField with an NSThemeFrame to return a custom text field (I used it to change the backgroundStyle on my F3X ).

0
source share

All Articles