Reading time: 2 min

This recipe shows all you need to do to support dark mode in your SwiftUI app. The end result looks like this:

preview

You should know that SwiftUI supports dark mode by default - which can be a blessing and a curse. Basically, if you're building a simple app without a lot of custom styling, most things should work out of the box - both foreground and background colors of all built-in views will change if you switch from light to dark mode or vice-versa. However, the moment you start customizing your colors a bit, you need to make sure to do it thoroughly and override each default behavior.

Alternatively, you can lock your app in the light appearance in Info.plist by adding UIUserInterfaceStyle key with value of Light.

Supporting adaptive colors

Adaptive colors are those that change based on the active user interface style, or ColorScheme as its known in SwiftUI. You can add those in two ways.

Adaptive color in Assets

The easiest (and probably the best) way is to just add a new Color Set to your Assets. There you can provide different colors for light, dark and default color schemes:

Screenshot%202021-08-31%20at%2009.19.01

Afterwards, just load the Color in your code:

extension Color {
  static let textColor = Color("TextColor")
}

And that's it! Just apply this color wherever you'd like and it'll automatically change if the interface style / color scheme does:

Form {
  Text("Some text")
  TextField("TextField", text: .constant("Some text field"))
  Toggle("Toggle", isOn: .constant(true))
}
.foregroundColor(.textColor)

Programatic adaptive color

If you want, you can also create an adaptive Color programatically. To do so, you need to bridge from UIColor, as it has an initializer that allows itself to update dynamically based on the current interface style / color scheme:

extension UIColor {
  convenience init(light: UIColor, dark: UIColor) {
    self.init { traitCollection in
      switch traitCollection.userInterfaceStyle {
      case .light, .unspecified:
        return light
      case .dark:
        return dark
      @unknown default:
        return light
      }
    }
  }
}

extension Color {
  init(light: Color, dark: Color) {
    self.init(UIColor(light: UIColor(light), dark: UIColor(dark)))
  }
}

Now you can define:

extension Color {
  static let defaultBackground = Color(light: .white, dark: .black)
}

and use the new adaptive color:

VStack {
  // ....
}
.background(Color.defaultBackground)
Custom UI based on color scheme

While adaptive colors should be enough for most use-cases, sometimes you might want to render different UI based on the current color scheme. Do do so, just tap into the environment value named colorScheme:

@Environment(\.colorScheme) var colorScheme: ColorScheme

var body: some View {
  Text((colorScheme == .dark) ? "Dark theme active" : "Light theme active")
}
Dark mode in previews and simulators

To simulate dark mode in an Xcode preview, use the colorScheme modifier (or preferredColorScheme if you're on iOS 15+):

struct DarkModeTestDark_Previews: PreviewProvider {
  static var previews: some View {
    DarkModeTest()
      .colorScheme(.dark) // HERE
  }
}

To enable dark mode on a Simulator, go to Settings → Developer and toggle Dark Appearance.

Next Post Previous Post