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.