Kuba Suder's blog on Mac & iOS development

Implementing Dark Mode on iOS

Categories: UIKit, WWDC 19 0 comments Watch the video

Traditionally iOS apps had hardcoded all colors

Now, since you need to use different variants depending on the appearance, it’s better to use semantic, dynamic colors

iOS does the work for you – chooses the right color and updates it automatically when appearance changes

Semantic system colors:

label – default label text color (black/white)

systemBackground – base background color, pure white / pure black

secondarySystemBackground, tertiarySystemBackground – slightly darker/lighter shades of gray to let you visualize the view hierarchy of your app

systemGroupedBackground – for table background

Base colors adapting to appearance: systemBlue, systemRed, …

Dynamic colors defined in asset catalogs with 2 versions

All UIColors contain the defined variants inside and update to the right one automatically

Images can also have a dark appearance variant in the asset catalog

Debug dark mode using the “Environment overrides” runtime panel in Xcode status bar

Materials with blurred translucent background & vibrancy

Use by creating a UIVisualEffectView with UIBlurEffect(style: .systemMaterial)

Material types: thick, regular, thin, ultrathin

Vibrancy types: primary, secondary, tertiary, quarternary (for text and fills)

To use, create another UIVisualEffectView with a UIVibrancyEffect inside the blur effect view’s contentView

Hierarchy: UIVisualEffectView (+UIBlurEffect)  ⭢  contentView  ⭢  UIVisualEffectView (+UIVibrancyEffect)  ⭢  contentView  ⭢  labels etc.

Your views can be in one of two “layers” of UI: the base level and the elevated level. Elevated level is used e.g. in modals presented as sheets.

In dark mode, backgrounds in the elevated level are slighly darker (primary background is not pure black)

UITraitCollection has a userInterfaceStyle (light/dark) and userInterfaceLevel (base/elevated)

To get the actual rendered color in a given context: dynamicColor.resolvedColor(with: traitCollection)

Dynamic colors in code:

UIColor { traitCollection in … return .black/.white }

For custom drawing, access the UITraitCollection.current property which UIKit sets for you

When you want to set e.g. a layer’s color to a cgColor, you can:

  • resolve it from a UIColor using resolvedColor()
  • tell traitCollection to update .current by calling: traitCollection.performAsCurrent { … }
  • update .current yourself

If you need to manually update the color when appearance changes, use traitCollectionDidChange() and do:

if traitCollection.hasDifferentColorAppearance(comparedTo: previous) { … }

UIImage does not store all variants inside, so ideally you should use UIImageView which manages the variants for you

If you need to resolve an image manually, do: image.imageAsset.image(with: traitCollection)

Trait collections

New behavior for trait collections when initializing views: iOS predicts the target trait collection for when the view is added to the hierarchy, and only calls didChange() when it actually changes

Debugging trait collection changes: add a launch argument -UITraitCollectionChangeLoggingEnabled YES

The best places to use traits are: viewWill/DidLayoutSubviews() and UIView.layoutSubviews(), traits are guaranteed to be resolved at those points

Forcing a specific appearance:

  • UIViewController.overrideUserInterfaceStyle (preferred)
  • UIView.overrideUserInterfaceStyle
  • UIPresentationController.overrideTraitCollection and UIViewController.setOverrideTraitCollection(_: forChild:) – only set the modified properties
  • for the whole app: UIUserInterfaceStyle key in Info.plist = Light/Dark

Status bar styles:

  • .darkContent – always dark (like .default before)
  • .default = automatic based on the userInferfaceStyle of the VC


  • .medium and .large styles instead of .gray, .white and .whiteLarge
  • updates automatically for the appearance
  • you can use color property to set a custom color

NSAttributedText: you need to manually set .foregroundColor = .label, otherwise you get black

Leave a comment


This will only be used to display your Gravatar image.


What property can you use on iOS to get a unique device ID that the user can reset in Settings?