Supporting Dark Mode: Adapting Images

When Apple announced Dark Mode at WWDC 2018, one of my first thoughts was: “Aha! So that’s why Apple has us all switching our icons to template (monochrome) style images.” These images do tend to adapt pretty well to Dark Mode because the system can simply invert them and obtain a fairly usable icon, but the crude inversion doesn’t work well in all cases.

There are a lot of details to really getting Dark Mode icons perfect, and Apple talks a lot about this in the Introducing Dark Mode WWDC session. Given my limited time and resources, I took a pragmatic approach to get things looking “good enough” so I could ship a cohesive project while I hope to continue working on refinements in the future.

As with colors, asset catalogs can be a great aid in managing image variations for different appearances. Use them if you can, but bear in mind the caveats mentioned in the Adapting Colors section of this series.

Most apps will probably require some refinement of toolbar icons, the row of images typically displayed at the top of some windows. In MarsEdit, I was in pretty good shape thanks to a recent overhaul for MarsEdit 4, in which Brad Ellis revised my toolbar icons. Many, but not all, of the images have a templated style aesthetic. Here’s MarsEdit’s main window in Light Mode:

Screenshot of MarsEdit 4's main window toolbar in Light Mode

Let’s see what happens when just switch to Dark Mode without any special care for the icons:

Screenshot of MarsEdit 4's main window toolbar in Dark Mode without any special finessing

That’s … not so good! Even my vaguely template-style icons are not being treated as templates, so they render with their literal gray colors and look pretty bad in Dark Mode. I realized I could probably do some quick Acorn work and get the template-style icons into shape, but what about the ones with splashes of color? The pencil? Should it still be yellow in Dark Mode?

I opted for a pragmatic, stop-gap solution. Without making any changes whatsoever to the graphics files, I worked some magic in code and came up with this:

Screenshot of MarsEdit 4's main window toolbar in Dark Mode with some finessing

That’s … actually pretty good! So what’s the magic in code I alluded to? I created a custom subclass of NSButton that will optionally set template status on the button’s image only if we’re in Dark Mode. You can see that some of the icons I’ve left untouched, because I felt their colors fit well enough in both dark and light modes. Here’s my custom RSDarkModeAdaptingToolbarButton:

class RSDarkModeAdaptingToolbarButton: NSButton {
   public var useTemplateInDarkMode: Bool = false
   var originalTemplateFlag: Bool = false

   public convenience init(image: NSImage, 
                           target: Any?, 
                           action: Selector?,
                           useTemplateInDarkMode: Bool = false) {
      self.init(image: image, target: target, action: action)
      self.useTemplateInDarkMode = useTemplateInDarkMode
   }

   override func layout() {
      // Always re-set the NSImage template state based
      // on the current dark mode setting
      if #available(macOS 10.14, *) {
         if self.useTemplateInDarkMode,
            let targetImage = self.image
         {
            var newTemplateState = self.originalTemplateFlag

            if self.effectiveAppearance.isDarkMode {
               newTemplateState = true
            }

            targetImage.isTemplate = newTemplateState
         }
      }

      super.layout()
   }
}

The key to this button’s functionality is the guarantee that layout will always be called on a button after an appearance change has occurred. This gives the button the opportunity to configure properties on itself that will affect how it is drawn. Sneaking in to set the template state on the image guarantees the NSButton superclass drawing code will treat it as a template in Dark Mode, but as a bitmap image in Light Mode.