NSButton disabled title color always gray - macos

I have a custom NSButton, but no matter what i do, the disabled color is always gray
I tried all solutions i came across
i'am setting the attributed string title with white foreground color (i looks like the color attribute is ignored for the disabled state)
i did set [[self cell] setImageDimsWhenDisabled:NO];
event when the documentations states
// When disabled, the image and text of an NSButtonCell are normally dimmed with gray.
// Radio buttons and switches use (imageDimsWhenDisabled == NO) so only their text is dimmed.
#property BOOL imageDimsWhenDisabled;
it doesn't work
My NSButton uses wantsUpdateLayer YES, so the draw methods are overwritten, but i don't understand, where the title is drawn

On OS X 10.9 I've managed to alter the color of the button's text when it's disabled by sub-classing the cell that draws the button.
Create a new NSButtonCell subclass in Xcode and override the following method:
- (NSRect)drawTitle:(NSAttributedString *)title
withFrame:(NSRect)frame
inView:(NSView *)controlView {
NSDictionary *attributes = [title attributesAtIndex:0 effectiveRange:nil];
NSColor *systemDisabled = [NSColor colorWithCatalogName:#"System"
colorName:#"disabledControlTextColor"];
NSColor *buttonTextColor = attributes[NSForegroundColorAttributeName];
if (systemDisabled == buttonTextColor) {
NSMutableDictionary *newAttrs = [attributes mutableCopy];
newAttrs[NSForegroundColorAttributeName] = [NSColor orangeColor];
title = [[NSAttributedString alloc] initWithString:title.string
attributes:newAttrs];
}
return [super drawTitle:title
withFrame:frame
inView:controlView];
}
Select the button in Xcode, then select its cell (maybe easiest to do this in the Interface Builder dock), now got to the Identity Inspector and set the cell's class to that of your subclass.

This is because of the default true value of
- (BOOL)_shouldDrawTextWithDisabledAppearance
Try to change this method instead of imageDimsWhenDisabled. If you are using Swift 4, I would do the following in the Bridging header:
#import <AppKit/AppKit.h>
#interface NSButtonCell (Private)
- (BOOL)_shouldDrawTextWithDisabledAppearance;
#end
And in the subclass of NSButtonCell:
override func _shouldDrawTextWithDisabledAppearance() -> Bool {
return false
}
And that's it: the grey should disappear

Related

Restore the visual state of an NSAttributedString after having clicked on it

I need to restore the visual state of an NSAttributedString after having clicked on it.
My NSAttributedString contains links attributed to ranges.
In this example the text "#user" has a a link to "htpp://somesite.com/":
let text = "Hey #user!"
let attr = NSMutableAttributedString(string: text)
let range = NSRange(location: 4, length: 5)
attr.addAttribute(NSForegroundColorAttributeName, value: NSColor.orange, range: range)
attr.addAttribute(NSLinkAttributeName, value: "htpp://somesite.com/", range: range)
let tf = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 50))
tf.allowsEditingTextAttributes = true
tf.isSelectable = true
tf.stringValue = text
tf.attributedStringValue = attr
It works well: click on "#user" in the text field, it launches the URL.
But once clicked, the attributed color disappears and is replaced by this blue one and an underline is added:
I can't find a solution to restore the original color once the string is clicked (or to avoid having this automatic change altogether).
I've seen this and this but there's no actual solution, and I can't integrate the pointed library to my project (I'd really like to not having to import any library, actually).
Note that my existing code is in Swift but I can use an Objective-C solution.
When the link is clicked, the text is displayed by the field editor. The default link text style in the field editor is blue and underlined.
Solution 1: change the text style of the link in an override of setUpFieldEditorAttributes: in a subclass of NSTextFieldCell.
- (NSText *)setUpFieldEditorAttributes:(NSText *)textObj {
NSText *fieldEditor = [super setUpFieldEditorAttributes:textObj];
if ([fieldEditor isKindOfClass:[NSTextView class]]) {
NSMutableDictionary *linkAttributes = [((NSTextView *)fieldEditor).linkTextAttributes mutableCopy];
linkAttributes[NSForegroundColorAttributeName] = [NSColor orangeColor];
[linkAttributes removeObjectForKey:NSUnderlineStyleAttributeName];
((NSTextView *)fieldEditor).linkTextAttributes = linkAttributes;
}
return fieldEditor;
}
Side effect: the field editor is shared by all controls in the window and all controls will now show orange links.
Solution 2: substitute your own field editor by using the fieldEditor:forObject: method or the windowWillReturnFieldEditor:toObject: delegate method of NSWindow. The text field has its own field editor and other controls won't have orange links. No subclasses of NSTextField or NSTextFieldCell required.
Example: (AppDelegate is the delegate of the window)
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (weak) IBOutlet NSTextField *textField;
#property (nonatomic, strong) NSTextView *linkFieldEditor;
#end
#implementation AppDelegate
- (NSTextView *)linkFieldEditor {
if (!_linkFieldEditor) {
_linkFieldEditor = [[NSTextView alloc] initWithFrame:NSZeroRect];
_linkFieldEditor.fieldEditor = YES;
NSMutableDictionary *linkAttributes = [_linkFieldEditor.linkTextAttributes mutableCopy];
linkAttributes[NSForegroundColorAttributeName] = [NSColor orangeColor];
[linkAttributes removeObjectForKey:NSUnderlineStyleAttributeName];
_linkFieldEditor.linkTextAttributes = linkAttributes;
}
return _linkFieldEditor;
}
- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client {
if (client == self.textField)
return self.linkFieldEditor;
else
return nil;
}
Solution 3: create a subclass of NSTextFieldCell, implement fieldEditorForView: and return your own field editor. This is similar to solution 2 but implemented by the cell instead of the window delegate.
Documentation on the field editor: Text Fields, Text Views, and the Field Editor and Using a Custom Field Editor.

Set font color of NSMenuItem to alternate when highlighted

This answer describes how to set the font, and thus the font color, of an NSMenuItem.
In order to alert the user to a problem with the selected item in a popup menu, I set the color to red. Works great, except when the item is highlighted, the background becomes blue, and my red-on-blue is hard to read and looks lousy. The font of regular menu items changes from black to white. I would like my modified menu item to change its font color when highlighted like that.
This is a dynamic menu. I set the font/color when items are created, in -menuNeedsUpdate. Of course, -[NSMenuItem isHighlighted] returns NO there because the item has just been created.
I also tried adding an observer on NSMenuDidBeginTrackingNotification and NSMenuDidBeginTrackingNotification, but that doesn't help either because these two notifications are always received in pairs, three to six pair each time I click the menu, and then after tracking has ended comes another -menuNeedsUpdate: which re-creates everything from scratch again. I'm not sure what it means when a menu is "tracking", but apparently it's not what I want.
I thought I'd ask if anyone has ever come up with a good answer for this, before I go off and do something really kludgey like these guys did for a similar NSMenuItem quandary.
You can implement the menu's delegate to be notified when an item is highlighted.
#pragma mark - NSMenuDelegate
- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item {
[menu.highlightedItem nik_restoreTextColor];
[item nik_overrideTextColor:[NSColor selectedMenuItemTextColor]];
}
It should be pretty straightforward to remove and re-add the color of a single item.
But here's the generic solution I'm using to remember and later restore the color:
#implementation NSMutableAttributedString(NIKExchangeAttribute)
- (void)nik_renameAttribute:(NSString *)originalAttribute to:(NSString *)newAttribute {
NSRange fullRange = NSMakeRange(0, self.length);
[self removeAttribute:newAttribute range:fullRange];
[self enumerateAttribute:originalAttribute
inRange:fullRange
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
[self addAttribute:newAttribute value:value range:range];
}];
[self removeAttribute:originalAttribute range:fullRange];
}
#end
static NSString *const ORIGINAL_COLOR_KEY = #"nik_originalColor";
#implementation NSMenuItem(NIKOverrideColor)
- (void)nik_overrideTextColor:(NSColor *)textColor {
NSMutableAttributedString *title = [self.attributedTitle mutableCopy];
[title nik_renameAttribute:NSForegroundColorAttributeName to:ORIGINAL_COLOR_KEY];
[title addAttribute:NSForegroundColorAttributeName
value:textColor
range:NSMakeRange(0, title.length)];
self.attributedTitle = title;
}
- (void)nik_restoreTextColor {
NSMutableAttributedString *title = [self.attributedTitle mutableCopy];
[title nik_renameAttribute:ORIGINAL_COLOR_KEY to:NSForegroundColorAttributeName];
self.attributedTitle = title;
}
#end

Change border "glow" color of NSTextField

I have an NSText field in MainMenu.xib and I have an action set to validate it for an email address. I want the NSTexFields border color (That blue glow) to be red when my action returns NO and green when the action returns YES. Here is the action:
-(BOOL) validEmail:(NSString*) emailString {
NSString *regExPattern = #"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,4}$";
NSRegularExpression *regEx = [[NSRegularExpression alloc] initWithPattern:regExPattern options:NSRegularExpressionCaseInsensitive error:nil];
NSUInteger regExMatches = [regEx numberOfMatchesInString:emailString options:0 range:NSMakeRange(0, [emailString length])];
NSLog(#"%ld", regExMatches);
if (regExMatches == 0) {
return NO;
} else
return YES;
}
I call this function and setting the text-color right now, but I would like to set the NSTextField's glow color instead.
- (void) controlTextDidChange:(NSNotification *)obj{
if ([obj object] == emailS) {
if ([self validEmail:[[obj object] stringValue]]) {
[[obj object] setTextColor:[NSColor colorWithSRGBRed:0 green:.59 blue:0 alpha:1.0]];
[reviewButton setEnabled:YES];
} else {
[[obj object] setTextColor:[NSColor colorWithSRGBRed:.59 green:0 blue:0 alpha:1.0]];
[reviewButton setEnabled:NO];
}
}
}
I am open to sub-classing NSTextField, but the cleanest way to do this would be greatly appreciated!
My solution for Swift 2 is
#IBOutlet weak var textField: NSTextField!
...
// === set border to red ===
// if text field focused right now
NSApplication.sharedApplication().mainWindow?.makeFirstResponder(self)
// disable following focusing
textField.focusRingType = .None
// enable layer
textField.wantsLayer = true
// change border color
textField.layer?.borderColor = NSColor.redColor().CGColor
// set border width
textField.layer?.borderWidth = 1
The way to go about this is to subclass NSTextFieldCell and draw your own focus ring. As far as I can tell there's no way to tell the system to draw the focus ring using your own color, so you'll have to call setFocusRingType:NSFocusRingTypeNone, check if the control has first responder status in your drawing method, and if so draw a focus ring using your own color.
If you decide to use this approach remember that the focus ring is a user defined style (blue or graphite), and there's no guarantee future versions of OSX won't allow the user to change the standard color to red or green. It's also likely the focus ring drawing style will change in future versions of OSX, at which point you'll have to update your drawing code in order for things to look right.

Text color of extra label in view-based NSTableView

In a view-based NSTableView, your custom row and cell views (subclasses of NSTableRowView and NSTableCellView) get their backgroundStyle property set, so you know if the background is light or predominantly dark (for the selected, highlighted row).
This even gets passed to immediate subviews.
Now, the default text label of the table cell view reacts correctly to this, so on a dark background, the text is drawn in a suitable light color.
However, an NSTextField added to provide extra text (with a custom text color set in Interface Builder) does not automatically adhere to this convention.
Is there a simple way in the API to get the text field to play nice, or do I have to subclass it?
Instead of overriding drawRect, you could also do this:
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
NSColor *textColor = (backgroundStyle == NSBackgroundStyleDark) ? [NSColor windowBackgroundColor] : [NSColor controlShadowColor];
self.detailTextField.textColor = textColor;
[super setBackgroundStyle:backgroundStyle];
}
See also here: http://gentlebytes.com/blog/2011/08/30/view-based-table-views-in-lion-part-1-of-2/
Just subclass NSTableCellView then implement drawRect:
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
if (self.backgroundStyle == NSBackgroundStyleDark) {
[yourTextFieldIVar setTextColor:[NSColor whiteColor]];
} else if(self.backgroundStyle == NSBackgroundStyleLight) {
[yourTextFieldIVar setTextColor:[NSColor blackColor]];
}
}

How to customize the selected text colors of an NSTextField / NSTextView in an inactive state

I'm using an NSTextField and customizing the fieldEditor using the setupFieldEditorAttributes: method. This allows me to set custom foreground and background colors for the selected text, which is important because my textField has a black background and white text. Generally, this works fine. However, my settings seem to be overridden when I deactivate the application and the window is no longer key. The fieldEditor NSTextView remains there, but drawing changes to a white text color and light gray selection color (the defaults). Does anyone have suggestions for how I can customize this drawing?
You can override [NSWindow willReturnFieldEditor:toObject:] and return there custom NSTextView with changed selection color.
Inspired by the answer to this question, the solution is to create an override of the NSLayoutManager that customizes the way in which the highlighting is performed based on the first responder state of the NSText view that owns it.
If the text view associated with this custom layout manager is the first responder, then it draws the selection using the color provided by macOS. If the text view is not the first responder, it uses the text view's background color as the selection color unless a custom color is provided via the setCustomInactiveColor method.
// ---------------------------------------------------------------------------
// IZLayoutManager CLASS
// ---------------------------------------------------------------------------
// Override NSLayoutManager to change how the currently selected text is
// highlighted when the owning NSTextView is not the first responder.
#interface IZLayoutManager : NSLayoutManager
{
}
-(instancetype)initWithOwningTextView:(NSTextView*)inOwningTextView;
#property (nullable, assign, nonatomic) NSTextView* owningTextView;
#property (nullable, strong, nonatomic) NSColor* customInactiveColor;
#end
#implementation IZLayoutManager
- (instancetype)initWithOwningTextView:(NSTextView*)inOwningTextView
{
self = [super init];
if (self) {
self.owningTextView = inOwningTextView;
}
return self;
}
- (void) dealloc
{
// my project is non-ARC; so we maually release any custom color
// we received; in non-ARC projects this is probably not necessary
if (self.customInactiveColor != NULL) {
[self.customInactiveColor release];
self.customInactiveColor = NULL;
}
[super dealloc];
}
// see extensive description of fillBackgroundRectArray in NSLayoutManager.h
// TL;DR: if you change the background color here, you must restore it before
// returning from this call
- (void) fillBackgroundRectArray:(const NSRect *)rectArray count:(NSUInteger)rectCount forCharacterRange:(NSRange)charRange color:(NSColor *)color
{
BOOL needToReestoreColor = NO;
if (self.owningTextView != NULL && [[self.owningTextView window] firstResponder] != self.owningTextView) {
if (self.customInactiveColor != NULL) {
[self.customInactiveColor setFill];
} else {
[[self.owningTextView backgroundColor] setFill];
}
needToReestoreColor = true;
}
[super fillBackgroundRectArray:rectArray count:rectCount forCharacterRange:charRange color:color];
if (needToReestoreColor) {
[color setFill];
}
}
#end
Then, after you've allocated the NSTextView, you need to do this:
NSTextView* myTextView = ... // get a reference to your text view
// allocate our custom layout manager
IZLayoutManager* layoutManager = [[[IZLayoutManager alloc] initWithOwningTextView:self] autorelease];
// if you want to use a color other than the background for
// the selected text, uncomment the following line and
// supply your desired color
// [layoutManager setCustomInactiveColor:[NSColor redColor]];
[[myTextView textContainer] replaceLayoutManager:layoutManager];

Resources