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
UIColor
usingresolvedColor()
- 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
andUIViewController.setOverrideTraitCollection(_: forChild:)
– only set the modified properties- for the whole app:
UIUserInterfaceStyle
key inInfo.plist
= Light/Dark
Status bar styles:
.darkContent
– always dark (like.default
before).default
= automatic based on theuserInferfaceStyle
of the VC
UIActivityIndicator
:
.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