Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSTextView draws black on almost-black in Dark Mode

When I build my macOS app in Dark Mode, some of my text views (NSTextView) render black text on a almost-black background. In Interface Builder, in the Attributes Inspector, the "Text Color" is set to to the system "Default (Text Color)", which I believe is correct. Indeed, in Interface Builder, this text renders white in Dark Mode and black in Light Mode, as desired. I've searched my code for any lines where I might be progammatically setting the text color in this view to black, but can't find any. Why is my text always black?

like image 221
Jerry Krinock Avatar asked Oct 16 '18 06:10

Jerry Krinock


3 Answers

I noticed that the errant text views have their "attributed string" bound, with Cocoa Bindings, to methods which return plain, not attributed NSString objects. I probably did this because I was lazy when I wrote this app years ago, and it worked fine. This mismatch turned out to be the problem. The fix is to modify these methods to return a NSAttributedString, with an attribute dictionary containing the key/value pair

NSForegroundColorAttributeName : NSColor.controlTextColor

Probably what happened is that Cocoa was designed to do what you probably want when a attributed string binding gets a non-attributed string. Instead of barfing an exception, Cocoa applies some "default" attributes, which include the black text color which has been the macOS default since 1984 – totally sensible until Dark Mode came along! Well, it might have been nice of Apple to change this default from black to controlTextColor, but apparently they did not.

Conclusion: We can no longer get away with binding the attributed string of a text view to a plain non-attributed string.

Or, you can use the answer of @Ely and bind to value. But if you try that, and do not see a value binding in the Bindings Inspector, but do see a data binding, it is because of these remarks in the NSTextField documentation:

[value] binding is only available when the NSTextView is configured to display using as a single font.

and later

[data] binding is only available when the NSTextView is configured to allow multiple fonts.

It turns what they mean by configured to allow multiple fonts is that, in the Attributes inspector, the Allows Rich Text checkbox is on. Conversely, configured to display using as a single font means that the Allows Rich Text checkbox is off.

like image 94
Jerry Krinock Avatar answered Nov 01 '22 15:11

Jerry Krinock


It worked for me after using this code (macOS Catalina version 10.15.3):

if #available(OSX 10.14, *) {         
    textView.usesAdaptiveColorMappingForDarkAppearance = true
} else {
    // Fallback on earlier versions - do nothing
}

I found this documented in the method:

/*************************** Dark Mode ***************************

When YES, enables the adaptive color mapping mode. In this mode under the dark effective appearance, NSTextView maps all colors with NSColorTypeComponentBased by inverting the brightness whenever they are coming in and out of the model object, NSTextStorage. For example, when rendering, interacting with NSColorPanel and NSFontManager, and converting from/to the pasteboard and external formats, the color values are converted between the model and rendering contexts. Note that the color conversion algorithm compresses the brightness range and, therefore, does not retain the round-trip fidelity between the light and dark appearances. It may not be suitable for rich text authoring, so it is a good idea to provide a command or preference for your users to see and edit their docs without this option, or in light mode. */

like image 20
Douglas Frari Avatar answered Nov 01 '22 15:11

Douglas Frari


If you use plain text in NSTextView (because you need the scrollview, for example), simply bind to the value property instead of attributedString. This binding will use the text color setting of the control, and works perfectly with Dark Mode.

like image 5
Ely Avatar answered Nov 01 '22 15:11

Ely