18
votes

For cocoa, I have an NSTableView set to be view based. When a row is selected, the text fields change their color to white. How do I keep it black?

I should also note that the Highlight is set to Source List (it does the same thing on Regular). Highlight Setting

Unselected row Unselected

Selected Row Selected Row

I was hoping for something similar to the state config for iOS:

enter image description here

This was suggested in WWDC 2011 Session 120 but it's a bit delayed so I'm not going to use it. It may work for someone else though.

- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
    [tableView enumerateAvailableRowViewsUsingBlock:^(NSTableRowView *rowView, NSInteger row){
        NSTableCellView *cellView = [rowView viewAtColumn:0];
        if(rowView.selected){
            cellView.textField.font = [NSFont boldSystemFontOfSize:14];
        }else{
            cellView.textField.font = [NSFont systemFontOfSize:14];
        }
    }];
}
7
Thanks. Unless I missed something, that seems more like background colors. I have done that a already for my custom background color. I'm looking for the color of the text.joels
Do the text field colors change back when the row is deselected? I'm trying to understand how and why this is happening.paulmelnikow
Yes, they change back. I believe this is normal behavior. I would normally use tableView:willDisplayCell:forTableColumn:row: for updating text colors so I would think I can check for row selection and update the text color in the tableView:viewForTableColumn:row: method. However, I was hoping to find a setting in IB that I can use to set the selection color, like how you can configure control states for the iOS.joels
You need to do the same in - (void)tableViewSelectionIsChanging:(NSNotification *)aNotification for this to work during mouse selection. Is this the delay you've been referring to?pointum

7 Answers

18
votes

There is no need for custom code to accomplish that.

Just set the color of the label to "label color" in Interface Builder. The automatic white/black thing only works if the label has the "Control Text Color" set and is in an NSTableCellView.

17
votes

Depending on why you need to do this, there are 2 approaches.

You can subclass NSTableRowView and override -[NSTableRowView interiorBackgroundStyle] to return NSBackgroundStyleLight. This will tell the cells that they are on a light background and to draw dark text, which will be black.

The other way is to subclass NSTableCellView and override -[NSTableCellView setBackgroundStyle:] and set the colors yourself there.

14
votes

Override NSTableCellView and add this method to change the text color when the cell is selected.

- (void) setBackgroundStyle:(NSBackgroundStyle)backgroundStyle
{
    NSTableRowView *row = (NSTableRowView*)self.superview;
    if (row.isSelected) {
        self.textField.textColor = [NSColor blackColor];
    } else {
        self.textField.textColor = [NSColor whiteColor];
    }

}
3
votes

I came up with a different solution. Subclassing NSTableCellView would have been fine if Cocoa supported @IBOutletCollection. Because then I could have one Cell subclass that has an array of all NSTextFields in the cell. But since I had many kinds of cells with varying numbers of NSTextFields I didn't like this option. Instead, I took a look at Apple's documentation for the backgroundStyle property in NSTableCellView.

The default implementation automatically forwards calls to all subviews that implement setBackgroundStyle: or are an NSControl, which have NSCell classes that respond to backgroundStyle.

If my TextFields implement setBackgroundStyle then they should get notified when the cell selection changes. However, this forwarding of the background style is not recursive. Because my NSTextFields were within NSStackViews they were not getting the message. To get around this, I just wrote an extension to implement setBackgroundStyle on all NSViews. It just forwards the message on. Lastly, I added an extension to NSTextField to also implement this method. From this extension, I change the text color and call super. This solution is also nice because no subclasses are needed. No subclasses of NSTableCellView or of NSTextField.

Adding this functionality to all views and to all NSTextFields could cause issues with NSTextFields that are not within NSTableViews changing color unexpectedly. But so far, only the ones within my TableViews/OutlineViews are changing color and thats exactly what I was looking for. If you see textfields changing color that you don't expect, you may want to subclass NSTextField and implement a setBackgroundStyle override only on that subclass instead of adding it to all NSTextFields.

The code in Swift 3 I used is pasted below.

extension NSView {
    func setBackgroundStyle(_ newValue: NSBackgroundStyle) {
        for view in self.subviews {
            view.setBackgroundStyle(newValue)
        }
    }
}

extension NSTextField {
    override func setBackgroundStyle(_ newValue: NSBackgroundStyle) {
        switch newValue {
        case .dark:
            self.textColor = NSColor.controlLightHighlightColor
        case .light, .lowered, .raised:
            self.textColor = NSColor.labelColor
        }
        super.setBackgroundStyle(newValue)
    }
}
2
votes

For my Swift app none of the above seemed to quite work correctly. This method handles the NSTableView losing focus correctly and when the window is not the key window but the cell is still selected.

Within the NSTableCellView subclass use the following:

override var backgroundStyle: NSView.BackgroundStyle {
  willSet {
    if newValue == .dark {
      title.textColor = NSColor.white
    } else {
      title.textColor = NSColor.labelColor
    }
  }
}
0
votes

Based on @sabes's answer, I created this NSTextFieldCell subclass which you can use to set your custom text colors when a row is selected or deselected. You can set the subclass of the relevant text field cell in IB.

@interface SNBlueTextFieldCell : NSTextFieldCell

@end

@implementation SNBlueTextFieldCell

- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
    [self setTextColor:(backgroundStyle==NSBackgroundStyleDark ? [NSColor blackColor] : [NSColor blueColor])];
}

@end
0
votes

In macOS 11 or higher, the .dark and .light background styles are deprecated. But you can use .emphased instead:

override var backgroundStyle: NSView.BackgroundStyle {
    willSet {
        textField.textColor = newValue == .emphasized ? .labelColor : .secondaryLabelColor
    }
}

In the example, .labelColor is the selected color, and .secondaryLabelColor the unselected color.