In the Dark Mode: a tortuous journey without a map

If you’re lucky/brave enough to be running the developer beta of Mojave, or about to become one of its public beta-testers later this month, Dark Mode will be one of the first things that you’ll want to try out. Despite my criticisms, it is impressive, and the WWDC demonstrations of it are attracting some pro users who were becoming disenchanted with Apple’s Mac developments of late.

As a developer, the first thing that you’ll want to test your apps against is Dark Mode, where there’s a good chance that something won’t work or look right. In my case, apart from one app (Woodpile) which is fairly completely broken by 10.14, my biggest problems are with scrolling text views, which almost universally appear as black text on a dark grey background, defeating their purpose.

The closest we have to documentation are the videos and slides from the WWDC sessions. If you’ve already sat and watched your way through session 210, Introducing Dark Mode, you’ll know that there is essentially nothing there about how to deal with this type of problem. Indeed, even in session 218, Advanced Dark Mode, it is slipped in right at the end, in Tips and tricks, starting at page 222 of the 255 in the PDF of the slides.

Apple pushes a consistent message throughout these descriptions of Dark Mode that you should not use hardcoded colours, like NSColor.black. I don’t, indeed in my scrolling text views I normally leave as much as possible set to defaults. Most of my apps then just push in the styled text as an attributed string, without any attributes:
let attr = NSAttributedString(string: myString as String)
self.theResTxt.textStorage?.setAttributedString(attr)

Given that Apple is telling developers not to use hardcoded colours like NSColor.black, it is extraordinary that Apple has decided that the default text colour is not NSColor.textColor, but NSColor.black. So unless I explicitly set all the attributed strings to be displayed in my scrolling text boxes to NSColor.textColor, they’ll all be displayed in black on dark grey, when in Dark Mode.

This even applies when you carefully set the text to be displayed in NSColor.textColor in Interface Builder. Changing settings there usually does nothing to the colour of text, which strikes me as impressively non-functional.

Move forward through the slides to page 230, and you’ll see what Apple recommends. Instead of code like
let attributes: [NSAttributedString.Key : Any] = [.font : NSFont.systemFont(ofSize: 36.0)]
which results in NSColor.black text, that should be changed to
let attributes: [NSAttributedString.Key : Any] = [.font : NSFont.systemFont(ofSize: 36.0), .foregroundColor : NSColor.textColor]

That same slide suggests using
textView.performValidatedReplacement(in: range, with: attributedStr)
which is, of course, available only in macOS 10.14, and that’s as much documentation as you’ll find about it too.

I wanted a simple, clean fix which could be applied in every version of macOS from El Capitan onwards. After a little fiddling around, I arrived at the simple replacement of
let attr = NSAttributedString(string: myString as String)
with the extra few characters in
let attr = NSAttributedString.init(string: myString as String, attributes: [.foregroundColor : NSColor.textColor])

If you’re pushing attributed strings without attributes into scrolling text boxes, I suggest that you try that.

I am greatly indebted to Randy Salinger, of Mothers Ruin Software, for pointing me in the right direction.