WWDC 19
Implementing Dark Mode on iOS
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
UIColorusingresolvedColor() - tell
traitCollectionto update.currentby calling:traitCollection.performAsCurrent { … } - update
.currentyourself
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.overrideUserInterfaceStyleUIPresentationController.overrideTraitCollectionandUIViewController.setOverrideTraitCollection(_: forChild:)– only set the modified properties- for the whole app:
UIUserInterfaceStylekey inInfo.plist= Light/Dark
Status bar styles:
.darkContent– always dark (like.defaultbefore).default= automatic based on theuserInferfaceStyleof the VC
UIActivityIndicator:
.mediumand.largestyles instead of.gray,.whiteand.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