Interested in learning how to implement the new Dark Mode for an iOS app? Check out the detailed tutorial from Metova developer, Arturo Diaz, below!

REQUIREMENTS

For your app to support Dark Mode, it must:

  1. Have been built and released with Xcode 11
 or later.
    If you use Xcode 10 (or earlier), the app will always display its normal appearance, even on devices with Dark Mode enabled.
  2. Run on an iOS 13 device
    Only devices running iOS 13 (or later) support Dark Mode, previous versions of iOS will display the default appearance.

YOU CAN FORCE ONE APPEARANCE OVER THE OTHER

If you really wish to avoid adopting dark mode in your app, drop the “light mode” indefinitely in favor of dark mode, or simply postpone your dark mode adoption to another time, add a new key UIUserInterfaceStyle in your app info.plist and set its value to Light or Dark.

OVERRIDING THE SYSTEM APPEARANCE

Sometimes it makes sense for a view to stick with one appearance, regardless of the system preference.

With iOS 13, UIView, UIViewController, and UIWindow have gained a new overrideUserInterfaceStyle property that lets us override the system appearance:

let view = UIView()

// this view will inherit the appearance of its superview

let darkView = UIView()

darkView.overrideUserInterfaceStyle = .dark

// this view (and its subviews) will always be in dark mode

overrideUserInterfaceStyle is an enum instance of type UIUserInterfaceStyle. These enum cases are either .dark, .light, or .unspecified, which is used to say that the view/viewController/window will inherit the interface from its superview/parentController/system.

Setting this property affects that specific view/viewController/window and anything that is “below” it.

By doing so, its view and all its sub views will adopt your preference instead of the system one.

In case you’d like a view to go back to listen to the system preference, set back the overrideUserInterfaceStyle property to .unspecified.

DON’T FORCE ONE APPEARANCE OVER THE OTHER

Using Apple’s guidance: “Only a small subset of apps really should be dark all the time, and those are media-centric or content-creation apps.” Unless you have a very good reason to offer just one interface appearance, make sure to always respect the system preference. It doesn’t matter how much you prefer one over the other.

DARK MODE IS NOT THE ONLY NEW MODE

This might have not hit the press as much as Dark Mode, however iOS has always had two modes: default and high contrast (you can enable this in any iOS device by going to Settings > Accessibility > Increase Contrast.

While you might be able to read with ease both left and right images, for someone else having some help (read: high contrast) would be great, for others it’s the only possible way to read anything at all.

Now that we are aware of all of the options, it’s clear that we have to take care of four appearances:

  1. Default
  2. Default High Contrast
  3. Dark Mode
  4. Dark Mode High Contrast

This is another reason why it is really important to stick with default UIKit (or SwiftUI) elements as much as possible:

  • If we do so, we get support for all of these appearances for free.
  • If we create our own colors and UI components, we must take care of each mode, element, and element state ourselves. This work grows exponentially.

CONFIGURATION

COLORS

At the end of the day, all our app does is throwing colors at the screen: getting colors right means having your app 99% ready for Dark Mode.

(Dynamic) System Colors

Until iOS 12, UIColor has offered us a few simple colors like .red, .yellow etc.  You don’t want to use these colors any longer. These colors are static, which means that their tint never changes.

Since iOS 13 and Xcode 11, Apple is introducing System Colors:

Top: System Colors in the default interface. Bottom: System Colors in dark mode.

From left to right: .systemBlue, .systemGray, .systemGreen, .systemIndigo, .systemOrange, .systemPink, .systemPurple, .systemRed, .systemTeal, .systemYellow.

As you can see from the picture above, contrary to the old static colors, system colors are dynamic: their tint will adapt to the current system interface.

But those are not the only new colors! We also have a full range of grayscale colors, where the difference between dark and light appearance is even more obvious:

Top: System Colors Grayscale in the default interface. Bottom: System Colors Grayscale in dark mode.

From left to right: .systemGray, .systemGray2, .systemGray3, .systemGray4, .systemGray5, .systemGray6.
By using these dynamic colors (e.g. UIColor.systemBlue instead of UIColor.blue), your interface will automatically pick the right tint for the current system preference, no further work required! 🎉

(Dynamic) Semantic Colors

Xcode 10 and earlier offered two colors named .lightText and .darkText.
The use of these is now discouraged, as they’re static, and will not adapt to any interface. Instead, from Xcode 11 we have a full new suite of semantic colors such as UIColor.label, UIColor.placeholderText, UIColor.systemBackground etc.
Instead of describing a shade, these color names are based on their intended usage: most of the time you want to use these, as, like system colors, they’re dynamic.
Most importantly, semantic colors ensure that your app has a similar appearance to the rest of the system. By using these, your app will feel native, which is always the best experience for the user.
The more you use these dynamic colors, the faster you’ll properly adapt to Dark Mode.

(Dynamic) Custom Colors: The Assets Catalog

This should always be your very last option to look at. This is the only option that requires a lot of work, trial, and error, for both you and the design team: “let Apple do the work for you, they’ve invested an unbelievable amount of time from their super talented teams on this, trust and use their work, your app will be fine”.

With the disclaimer out of the way, how do you define and use your own dynamic colors?

Since iOS 11 and Xcode 9 we can add colors into asset catalogs.

Now you can also define a dark variant for each color in there as well:

  • Select a color in your assets catalog, open the Attribute Inspector, and set its appearance from None to Any, Dark:

At this point a new color box for the Dark appearance will appear: congratulations! You have just created your first dynamic color! Set each variant to its appropriate tint and you’re good to go. Don’t forget to enable high contrast as well:

(Dynamic) Custom Colors In Code

If you’re still targeting iOS 10 (which doesn’t support colors declarations in the asset catalog) or don’t want or can’t use the options above, then the very last option available is to define colors in code:

let dynamicColor = UIColor { (traitCollection: UITraitCollection) -> UIColor in

    switch traitCollection.userInterfaceStyle {

    case .unspecified, .light: 

return .white

    case .dark: return .black

    }
}

Don’t do this, drop iOS 10 and use assets catalogs.

IMAGES

Almost every app displays images and/or symbols in the UI. Unless those images display user content (like a profile picture), then it’s a very good design to have one image variant for each mode. Let’s see how.

SF Symbols

Apple introduced SF Symbols at WWDC19. SF Symbols is a huge collection of glyphs that are available for developers to use in their own apps.

Apple itself uses SF Symbols in every stock app like Reminders, News, Maps and more:

Not only Apple uses them, but it also encourages us to do so, too. In Apple Words:

“The system uses SF Symbols, which automatically look great in Dark Mode, and full-color images that are optimized for both light and dark appearances. Use SF Symbols wherever possible.”

Beside being vector images, which look perfect at any size, they also come built-in with the system: no need to package them up in our app.

If you’re using storyboards, you can tell Xcode which symbol to use by typing the correct glyph name in the image name field (find the name in Apple’s SF Symbols app available in Apple SF Symbols guideline).

Alternatively, you can fetch any of them by using the new api UIImage(systemName:):

UIImage(systemName: "star.fill")

Other Images

For all other kinds of images that are not template images or symbols such as photos and more, we can follow the same steps as for custom colors: set their appearance to Any, Dark in the assets catalog and drop a new variant for each appearance.

Note that adding a dark variant to any assets in the catalog doesn’t break retrocompatibility: older iOS versions will always choose the asset tagged with Any Appearance.

DEBUGGING DARK MODE

Storyboards/.xib’s

In storyboards and .xib files, beside choosing the device and orientation to preview your screens in, you can now also toggle the interface preference between light and dark:

SIMULATOR

In Xcode 11 there’s a new button in the debugging toolbar: this button lets you access to an Environment Overrides popup which lets you, among other things, change font size and switch interface mode, you can even enable high contrast.

REFERENCES