Tips and tricks for implementing Dark Mode on iOS
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
- Compile your code on Xcode 11 or above
- 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 howUIStatusBarStyle
is set) - 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)
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
):
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):
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:
Now we have a much better, autocompleted, type-safe way of programatically accessing our Named Colors:
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+!):
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:
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:
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…