Tips and tricks for implementing Dark Mode on iOS

Aron Budinszky
hoursofoperation
Published in
4 min readMar 6, 2020

Enabling Dark Mode is easy. But implementing and maintaining a beautiful, complex UI with tons of color- and image assets can be a challenge.

The following article describes a few tips and tricks on implementing Dark Mode so you can manage your app’s color- and image assets in a consistent, safe, and maintainable way (including tips for SwiftUI’s Color and Image classes).

Enabling Dark Mode

  1. Compile your code on Xcode 11 or above
  2. In case someone previously disabled dark mode, you’ll have to remove the key UIUserInterfaceStyle (User Interface Style) from your Info.plist (and you might also want to consider how UIStatusBarStyle is set)
  3. Migrate your project to target iOS 11 or higher (without this step you’ll have to live without named color assets; not a blocker, but makes things more complicated)
Photo by Daniel Korpai on Unsplash

Named Color Assets

Define color assets

Named Colors are assets just like your image assets — you can visually add, name them in your Asset Catalog, and then programatically (or in Storyboard, or in SwiftUI) use your defined colors.

Named Colors can have different light and dark versions for each color — just choose Appearances / Any, Dark on the right in the Inspector, then select an appropriate color for each mode (Any being light mode, Dark being, well, dark mode).

Each app is different, but it’s best if you try to strike a balance between reusing colors while also separating context where necessary. For example, even though a header text and button text color might be the same for now, their context is completely different and they may be modifed to differing colors in the future.

As such, it is typically better to use contextual naming rather than names based on currently defined colors (so for example use rmAppBar instead of rmDarkBlue):

Choose Appearance / Any, Dark to enable Light/Dark mode versions of your colors. Try to use contextual names like rmLabel instead of color-based names like rmDarkGrey

You’ll also want to stick to those contexts. Don’t be lazy and reuse a color with the term “Label” in it just because it is the same as the background of a table. :)

Finally, a little tip: do check out the various ways of defining colors in the Asset Catalog. For example, it is possible to set colors using hex codes (which can come in handy when style guides as such):

Tip: you can also define your color assets using hex code…

Use color assets

Now that you have named color assets you can use them directly in your code with UIColor(named: “rmAppBar”) or for SwiftUI Color(“rmAppBar”). Note that UIColor will return an optional. While Color is not optional it will throw a runtime warning if the name could not be found (and just ignore the setting when rendering).

But there is a better way! We generally want to avoid string literals so we can avoid typos while also taking advantage of refactor, autocomplete, contextual search, etc. To do this we’ll need an extension for UIColor and Color along with an enum that defines our Named Color assets:

Extensions for Color and UIColor to avoid string literals when accessing Name Colors

Now we have a much better, autocompleted, type-safe way of programatically accessing our Named Colors:

Use extensions (see above) to avoid string literals when accessing your color assets.

Standard User Interface Element Colors

iOS 13 introduces UI Element Colors which provide a set of default interface colors that include light and dark mode definitions. So, using UIColor.links will give you the standard sytem color for links and will automatically take into account whether you are in light or dark mode.

Instead of using these directly, I would still recommend creating your own Named Color assets and then setting it to whichever UI Element Color you prefer (don’t forget it is only iOS 13+!):

You’ll find UI Element Colors under “iOS System Colors”. These built-in colors define both a light and a dark mode.

Image assets

Define image assets

Like with Color Assets, dark-enabled apps will now support two separate assets — unsuprisingly one for each mode :). Again, simply choose Appearances / Any, Dark on the right in the Inspector to enable dark versions.

In case your image asset does not need a separate versions for dark mode (like a logo for example) simply leave the Appearances setting as None.

Use image assets

For image assets the same applies as for named color assets — you’ll want to avoid string literals to stay safe. Here are the equivalent extension implementations for Image and UIImage:

Extensions for Image and UIImage to avoid string literals when accessing assets

Using assets in Storyboards

Above I focussed on programmatic (and SwiftUI) implementation, but Named Color assets also work well in Storyboards. As long as you are within your main bundle (see below for challenges with Frameworks) both Image and Named Color assets should just show up in your Storyboards’ relevant picker fields:

Be sure to only use assets from your main bundle; Framework-defined assets will still show up here, but will not load properly!

Using Framework-defined color and image assets

Both Image assets, Named Color assets require some workarounds if you want to use ones that are defined outside of your main bundle (in a Framework for example). This will be addressed in an upcoming article…

--

--