Dynamic colors on iOS

Dynamic colors

Dynamic colors are changing its content depending on the application appearance mode (Any (unspecified), Light or Dark). The API is available from iOS 13.

Apple provides colors out-of-the-box that are handy to use with default UI elements.

Color Area of usability color API
Label text with a primary content label
Secondary label text with a secondary content secondaryLabel
Link link colors link

There are many colors with own system coding for labels, background, separators covering all basic UI needs but sometimes a developer needs to define a custom one. It can be done via Xcode assets:

Set image appearanceĀ 

Or with UIColor dynamic provider:

var dynamicColor = UIColor.init { (traitCollection) -> UIColor in
            return traitCollection.userInterfaceStyle == .dark ? .white : .black
        }

We can derive userInterfaceStyle: UIUserInterfaceStyle (any, light, dark) from traitCollection: UITraitCollection .

UITraitCollection describes the interface environment with a collection of traits (size classes, display scale, user interface style, user interface idiom, preferred content size etc).

From iOS 13 the trait collection has a UITraitCollection.current class member which defines a current UI environment. It's a thread-local variable so it's safe and fast-enough to use on any thread.

The dynamic color can be resolved with a specific trait collection.

dynamicColor.resolvedColor(with: UITraitCollection.current)

It's important to note that Core Animation objects: CALayer , CATextLayer , CAGradientLayer etc, NSAttributedText and drawRect: would not reflect change of the current trait collection and it's needed to be updated explicitly.

Each layer in a view hierarchy has adopted protocol UITraitEnvironment that presents UITraitCollection for: UIScreen, UIWindow, UIViewController, UIPresentationController, UIView.

Each layer can have overwritten property preferredUserInterfaceStyle: UIUserInterfaceStyle and a listener of UITraitEnvironment updates:

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
            // Resolve dynamic colors
            // Apply colors for Core Animation objects
        }
    }

It listens for a change of collection and gives a previous trait collection value to compare with and filter out only changes related to colors because there can be any trait change.