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:
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.