Set font color of NSMenuItem to alternate when highlighted

1.7k views Asked by At

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.

1

There are 1 answers

0
nschum On

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