How can I set [NSTextView selectedTextAttributes] on a background window?

You can override the colour by overriding drawing method of NSLayoutManager.

final class LayoutManager1: NSLayoutManager {
    override func fillBackgroundRectArray(rectArray: UnsafePointer<NSRect>, count rectCount: Int, forCharacterRange charRange: NSRange, color: NSColor) {
        let color1 = color == NSColor.secondarySelectedControlColor() ? NSColor.redColor() : color
        color1.setFill()
        super.fillBackgroundRectArray(rectArray, count: rectCount, forCharacterRange: charRange, color: color1)
        color.setFill()
    }
}

And replace NSTextView's layout manager to it.

textView.textContainer!.replaceLayoutManager(layoutManager1)

Here's full working example.


As @Kyle asks for reason of setFill, I add some update.

From Apple manual:

... the charRange and color parameters are passed in merely for informational purposes; the color is already set in the graphics state. If for any reason you modify it, you must restore it before returning from this method. ...

Which means passing-in other color into super call has no effect, and you just need to NSColor.setFill to make it work with super call. Also, the manual requires to set it back to original one.


It's not when the window is in the background it's when the NSTextView is not selected. I don't think you can change that behavior. enter image description here

You could create an attributed string and add the NSBackgroundColorAttributeName attribute to the range of the selected text when it loses focus. The attributed string stays the same color even when the focus is lost.

NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"hello world"];
[string addAttribute:NSForegroundColorAttributeName value:[NSColor redColor] range:NSMakeRange(1, 7)];
[string addAttribute:NSBackgroundColorAttributeName value:[NSColor yellowColor] range:NSMakeRange(1, 7)];
[self.myTextView insertText:string];

enter image description here

EDIT by Abhi Beckert: this is how I implemented this answer (note I also had to disable the built in selected text attributes, or else they override the ones I'm setting):

@implementation MyTextView

- (id)initWithCoder:(NSCoder *)aDecoder
{
  if (!(self = [super initWithCoder:aDecoder]))
    return nil;

  // disable built in selected text attributes
  self.selectedTextAttributes = @{};

  return self;
}

- (id)initWithFrame:(NSRect)frameRect textContainer:(NSTextContainer *)container
{
  if (!(self = [super initWithFrame:frameRect textContainer:container]))
    return nil;

  // disable built in selected text attributes
  self.selectedTextAttributes = @{};

  return self;
}

- (void)setSelectedRanges:(NSArray *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
{
  // remove from old ranges
  for (NSValue *value in self.selectedRanges) {
    if (value.rangeValue.length == 0)
      continue;

    [self.textStorage removeAttribute:NSBackgroundColorAttributeName range:value.rangeValue];
  }

  // apply to new ranges
  for (NSValue *value in ranges) {
    if (value.rangeValue.length == 0)
      continue;

    [self.textStorage addAttribute:NSBackgroundColorAttributeName value:[NSColor yellowColor] range:value.rangeValue];
  }

  [super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
}

@end