Home · iOS & Swift Tutorials

Supporting Dark Mode: Adapting Your App to Support Dark Mode

In this tutorial, you’ll learn how to add support for Dark Mode in your app, making it look great in low-light environments.

4.9/5 15 Ratings

Version

  • Swift 5, iOS 13, Xcode 11

When Apple introduced the modern and flat design of iOS7 in 2013, many people objected to its user interface’s excessive whiteness. They preferred the more natural-looking materials of previous versions of iOS.

In the years leading to up iOS 13, many apps added Dark Mode, a new color theme, to compensate for the system’s brightness. This theme helped people use apps in environments with lower light.

Fortunately, in iOS13, Apple decided to grant this feature system-level love. The engineers at Apple added a toggle for users to switch between light and dark appearance. They also updated all pre-installed apps to respect the user’s choice.

Maybe Jony Ive took all the white rooms when he left! Apple decided to embrace the darkness, so why don’t you?

In this tutorial, you’ll add Dark Mode to Dark Arts, an app with information about all the teachers of Defense Against the Dark Arts at Hogwarts.

Specifically, you’ll cover how to:

  • Use colors; system, semantic, dynamic, background and foreground.
  • Support elevation levels act in dark mode.
  • Use different assets in light and dark mode.
  • Utilize the power of SF Symbols.
  • Opt out of dark mode, should you wish to do so.
  • And more..

Getting Started

Download the starter project for this tutorial by clicking the Download Materials button at the top or the bottom of the page. Double-click DarkArts.xcodeproj to open the project in Xcode.

Get familiar with the source code and file structure of the project.

Build and run. You’ll see a list of all the Defense Against the Dark Arts teachers sorted by the year Harry Potter and friends studied with them. Tap any item to see more information.

Dark Arts First Run

The app’s user interface is shiny and bright. You’ll remove this as you move through the tutorial.

To see how the app currently looks in Dark Mode, run the app in the simulator and change the interface style as follows.

When you run your app via Xcode, a set of debugging buttons appears at the bottom. Click the Environment Overrides button and a pop-up will appear. There’s an Interface Style toggle at the top of the pop-up.

Dark Arts - Xcode - overriding environment

Now toggle the Interface Style switch and click Dark. The app will look like this:

Dark Arts - First run, Dark

Not as dark as you hoped, huh? At least the navigation bar background switched to black. But the list stayed white.

System Colors

The most important aspect of adopting a new theme is working with colors. In iOS 13, Apple overhauled colors and introduced a couple of new concepts.

For as long as UIKit has existed, it has provided some predefined colors, such as .red, .blue and .yellow, which you could access statically on UIColor.

Now there’s a new palette of colors prefixed with the word system, including .systemRed and .systemBlue. These colors provide suitable and legible shades on both light and dark backgrounds.

Look at this table from Apple Human Interface Guidelines. As you can see, each system color, also known as a tint color, has a different RGB color code for light and dark modes.

An overview of system colors in Light and Dark mode.

Tap Alastor Moody in the app. In the detail screen, the name of the teacher’s Hogwarts house appears in the color of that house’s flag. For example, Alastor Moody is from Hufflepuff so the flag is yellow.

Dark Arts, teacher detail: Alastor Moody

You probably can’t read the house name because the color is the old .yellow. It wasn’t adapted to look good on light and dark backgrounds, but you’ll soon fix that.

Open TeacherDetailViewController.swift. In viewDidLoad(), the text color of houseLabel is set.

Change all the colors to their respective system variant:

switch teacher.house {
case .gryffindor:
  houseLabel.textColor = .systemRed
case .hufflepuff:
  houseLabel.textColor = .systemYellow
case .ravenclaw:
  houseLabel.textColor = .systemBlue
case .slytherin:
  houseLabel.textColor = .systemGreen
}

Build and run. Once again, tap Alastor Moody. Now, you can see he’s from Hufflepuff.

Dark Arts - Alastor Moody, revised

Semantic Colors

In addition to the newly introduced system colors, iOS 13 also provides semantically defined colors. A semantic color conveys its purpose rather than its appearance or color values. Therefore, a semantic color also automatically adapts to Dark Mode.

You don’t have to know these colors’ real raw values. Instead, you use them based on their intention and access them as you would other colors: through static calls on UIColor. Examples of semantic colors include .label, .separator, .link, .systemBackground and .systemFill.

Background Colors

iOS defines two sets of background colors: system and grouped. Each contains primary, secondary and tertiary variants that help you convey a hierarchy of information.

In general, use the grouped set of background colors when you have a grouped table view. Otherwise, use the system set of background colors.

With both sets of background colors, you use the variants to show hierarchy in the following ways:

  • Primary for the overall view.
  • Secondary for grouping content or elements within the overall view.
  • Tertiary for grouping content or elements within secondary elements.

To see these in action, you’ll now add background colors to the app — this time using a Storyboard.

Open Main.storyboard. In the Dark Arts scene, set the Background for both Table View and TeacherCell to System Background Color.

Dark Arts - Set background color for TableView

Build and run to see the effect in both light and dark modes. As you can see, the list’s background color automatically changes based on the appearance, but the texts aren’t visible in Dark Mode.

You’ll fix that next.

Dark Arts - Background colors - both modes

Foreground Colors

For foreground content, such as labels, you can also use various levels of semantic color to convey the importance of the content. Examples of leveled colors include .label, .secondaryLabel, .tertiaryLabel.

Once again, open Main.storyboard. Then in the Dark Arts scene, set the Color Name Label to Label Color.

Dark Arts - semantic color for label

Build and run. See the teachers’ names in both light and dark modes.

Pro tip: You can switch the simulator between light and dark mode without going back to Xcode. In the simulator, choose Features ▸ Toggle AppearanceShift-Command-A — to switch between them.

Dark Arts - semantic labels. Fixed in teachers list

Nice! With only a few changes, your app is now infinitely better at supporting Dark Mode.

But you can do more!

Elevation

Elevation is the distance between two layers of user interface on the z-axis. In light mode, developers use shadows to enhance depth perception when one layer of user interface is above another.

But that doesn’t work in Dark Mode. While a black drop shadow isn’t visible enough, a lighter shadow for a dark layer doesn’t look right.

To solve this problem, Dark Mode uses two sets of background colors: base and elevated. Base colors are darker, so background interfaces look receded. In contrast, elevated colors are lighter which makes foreground interfaces look protruded.

You can see an excellent example of this concept in action in Modal sheets.

Open Main.storyboard. In Teacher Detail View Controller, set Background for the root View to System Background Color. This is exactly the same value you set for the list background in previous steps.

While you’re here, change the following as well:

  • First, set the Color of Name to Label Color.
  • Second, set the Color of Taught at Hogwarts to Tertiary Label Color.
  • Third, set the Color of Years at Hogwarts to Secondary Label Color.

Dark Arts - setting semantic colors in the detail view.

Build and run. Tap a row to see the details screen.

Dark Arts - Modal

Look at the background colors of the view controller, which is on the back, and the detail screen, which is in front. Although you set both of them to the same .systemBackgroundColor, they look different. It’s more obvious in Dark Mode.

Remember, the list in the app had a pure black background in Dark Mode with .systemBackgroundColor. But in the details screen, when presented as a modal on top of its parent, it has a dark gray background color. This behavior is normal, and given to you without any extra effort.

Dark Mode is dynamic. That means the background color automatically changes from base to elevated when an interface, like a popover or modal sheet, is in the foreground.

Dynamic Colors

While these newly introduced system and semantic colors are useful, they won’t work in all situations. For example, they won’t help you if you need to use brand colors or your design calls for colors other than those Apple provides.

Before Dark Mode, you could incorporate custom colors in several ways. Developers often use code or Asset Catalogs when using UIColor initializers. Fortunately, Apple updated both to take Dark Mode into account.

You’ll tackle the in-code method first.

UITraitCollection

Before continuing, though, you first need to understand the concept of UITraitCollection.

iOS exposes interface environments for any app through the traitCollection property of the UITraitEnvironment protocol. UIWindow, UIViewController and UIView are all classes that conform to this protocol.

You can access many user interface characteristics in an iOS app, such as size classes, force touch capability and style. By taking those properties and related methods into account, you can adapt your user interface to what the system suggests.

Dark Mode is handled under the hood by the magic of trait collections. It’s a new trait, and you’ll learn how to use it next.

UIColor Dynamic Provider

To construct colors in code, you’ll use a closure based initializer.

Open TeacherDetailViewController.swift. Replace this line of code in viewDidLoad():

headerBackgroundView.backgroundColor = .white

With this:

headerBackgroundView.backgroundColor =
  // 1
  UIColor { traitCollection in
    // 2
    switch traitCollection.userInterfaceStyle {
    case .dark:
      // 3
      return UIColor(white: 0.3, alpha: 1.0)
    default:
      // 4
      return UIColor(white: 0.7, alpha: 1.0)
    }
  }

Here’s a breakdown:

  1. You call the new initializer of UIColor which takes a closure with a single input of type UITraitCollection. iOS calls this block and, based on the app’s user interface style, you get a specific color. If the user changes the system interface style, the callback is automatically called again and will change the color accordingly.
  2. You switch on the userInterfaceStyle property of traitCollection to know what appearance mode the app is in.
  3. If the app is in Dark Mode, it returns a darker shade of gray.
  4. If the app is not in Dark Mode, it returns a lighter shade of gray.

Build and run. Tap a row to see the header for the detail screen. Then switch to Dark Mode and see the reflected changes.

Dark Arts - dynamic UIColor in code

Creating colors in code is fine if you don’t want to use them in Interface Builder. If you plan on using Interface Builder, you’ll need an Asset Catalog.

Asset Catalog

Since iOS 11, you can save your colors in Asset Catalogs and use them both in code and Interface Builder. You can make your colors dynamic with a simple tweak.

Open Assets.xcassets and look at the colors folder. In the Attributes inspector, under the new Appearances section, you can add variations for a color. The simplest option would be Any, Dark, where you provide a new color for the dark appearance. For colors you’ll find a dynamic color named thumbnail-border. It provides a green color for dark mode and a gray color for light mode.

Dark Arts - asset catalogs, dynamic color

Where are you going to use this color, you ask? You’ll soon see.

Open Main.storyboard. In the Dark Arts scene, there’s a Border View which acts as the stroke around the teachers’ avatars. Currently, its Background is set to a static color: rw-dark. Since you don’t leave the smallest detail behind, change it to thumbnail-border.

Dark Arts - dynamic colors - border

Now, build and run. See how the teachers pop even more in Dark Mode.

Dark Arts - fixed border

Dynamic Images

You might have noticed a beautiful painting of Hogwarts at the bottom of the detail page. Wouldn’t it be cool if it showed Hogwarts at night when the app is in Dark Mode?

Great news! You can do that with dynamic images.

Follow the same procedure you used to add a variation to a color in the Asset Catalog for images.

Open Assets.xcassets. You’ll see two images named hogwarts and hogwarts-night. Click hogwarts.

In the Attributes inspector, click Appearances and choose Any, Dark. A new empty slot appears.

Right-click hogwarts-night and click Show in Finder. Drag the image from Finder to the empty slot you created in hogwarts. Now delete hogwarts-night since you have both images under the name hogwarts.

Dynamic images - asset catalog

Build and run. Enjoy the beautiful moonlight of Hogwarts, but stay away from Lupin.

Dark Arts - Dynamic image - lupin

SFSymbols

One of the coolest additions Apple introduced for iOS 13 was SFSympols, a huge set of consistent and highly configurable images. These symbols scale well based on appearance, size and weight. And best of all, you can also use them in your apps for free.

They’re also Dark Mode ready. You can tint them to any dynamic color, and they adapt beautifully. Unless your app needs specific images and assets, SFSymbols can be your go-to for your illustrations needs.

Time to add some flare to the details screen by adding a house icon beside the house name using SFSymbols.

Open TeacherDetailViewController.swift. In the last line of viewDidLoad(), add:

houseImageView.image = UIImage(systemName: "house.fill")

This line of code uses the new initializer for UIImage which takes the name of a symbol in SFSymbols’ catalog.

Build and run. See the beautiful tiny house icon. It’s not as fancy as Hogwarts’ common rooms but, it does the job.

Dark Arts - SFSymbols

While the house is beautiful in light mode, it’s black and hard to see in Dark Mode. You know how to fix that problem!

Above the line you just inserted, there’s this piece of code which sets the tintColor of houseImageView.

houseImageView.tintColor = .black

Replace it with:

houseImageView.tintColor = houseLabel.textColor

This line sets the houseImageView‘s tintColor to the textColor of houseLabel. Remember, you used system colors for that label so it dynamically adapts to the device appearance.

Build and run. See the home icon now switches color as you switch appearance modes.

Dark Arts - SFSymbols, system colors

Wondering how this happened? It’s the magic of SFSymbols. Remember, they scale well and adapt to your needs. You can use them at any size or color you desire.

Note: Apple provided a companion Mac app for SFSymbols. You can download it here and browse the catalog. The string name you used in UIColor’s initializer came from this app.

Dark Arts - SFSymbols

Opting Out of Dark Mode

Dark Mode is more popular than ever. You can set a device to Dark Mode system-wide, and all Apple and many third-party apps now include it. While you can opt-out of Dark Mode, you might cause eye strain for users who prefer everything in dark. We really encourage you to reconsider before opting out of supporting Dark Mode.

However, if you’re certain you want to opt-out of Dark Mode, you have a few options:

  1. Turn Dark Mode off for the whole app by using the UIUserInterfaceStyle key in Info.plist.
  2. Set the interface style on the app’s UIWindow, which usually means the whole app.
  3. Set the interface style for a specific UIView or UIViewController.

You’ll set these for the app one by one.

Note: The final project will stay dark since it breaks the purpose of this tutorial. Also, as you go through each option below, remember to remove the code for opting out of dark mode before trying a new way.

Opting Out with Info.plist

Open Info.plist. Then add a key named UIUserInterfaceStyle and set it to Light.

Build and run the app. Everything shines as if you’d done nothing for Dark Mode.

Dark Arts - Opt out, Info.plist

Remember to remove the above key before continuing to the next step.

Opting Out in UIWindow

Open SceneDelegate.swift. Replace this:

var window: UIWindow?

with:

var window: UIWindow? {
  didSet {
    window?.overrideUserInterfaceStyle = .light
  }
}

Since this app uses storyboards, the system sets the window.

This block of code sets a Swift property observer on window. As soon as this property is set, you override the interface style to .light. Since this app only uses a single window, overriding the style on the window causes the app to behave as if you set that key in Info.plist.

Build and run to confirm.

Dark Arts - opt out, UIWindow

Remember to remove the above code before continuing to the next step.

Opting Out in UIViewController

Open TeacherDetailViewController.swift. You’ll make this view controller a rebel that doesn’t respect Dark Mode.

In viewDidLoad(), right after the call to super.viewDidLoad(), insert:

overrideUserInterfaceStyle = .light

This line overrides the style for this specific view controller.

Build and run. Check out the Dark Mode! The list page is dark while the detail page is light.

Dark Arts - Opt out, specific view controller

Where to Go From Here?

You’ve taken a long journey into darkness. Now those Defense Against the Dark Arts teachers have something to battle!

You can download the final project by using the Download Materials button at the top or bottom of this tutorial.

Dark Mode is a simple concept with many smaller parts. To learn more, check out these resources:

I hope you enjoyed this tutorial. If you have any questions or comments, please join the discussion below!

Average Rating

4.9/5

Add a rating for this content

15 ratings

More like this

Contributors

Comments