Vikash Anand
8 min readJul 3, 2019

Dark Mode — iOS 13+

Apple has finally brought Dark mode in iOS 13 and i have been using it for a while and it looks gorgeous 😍.

Apple has a WWDC session for Dark Mode in which they have explained in detail about how applications can support Dark mode for iOS 13.In this article i will be sharing the information from this video only. So lets start…

iOS dark mode design system consists of 3 things. Semantic dynamic colors, Materials and Built-in views and controls provided by UIKit that uses these Semantic dynamic colors and materials.

  1. Semantic dynamic colors — These are new set of dynamic colors introduced that automatically adopt to the current display mode of the system i.e. light or dark mode.Previously these colors had a hard-coded value but now that has changed and dynamic colors have multiple values. Few examples of dynamic colors are

Background Dynamic Colors — .systemBackground, .secondarySystemBackground and .tertiarySystemBackground

Text Dynamic Colors — Primary, Secondary, Tertiary and Quaternary.

These dynamic colors are categorised based on the various use cases like Primary text color could be used for Title, Secondary text color could be used for subTitle, Tertiary text color could be used for Placeholder text and Quaternary text color could be used for disabled text.

2. Materials — Materials are a combination of blur and vibrancy effect(effect that lets some of the background material pass through).How to apply this blur and vibrancy effect on a view is present in the sample code(VAVisualEffectView).Link of the sample code is provided at the end of the page.

Note — Design for dark mode has two different levels for background color, base and elevated. When a view appears on screen edge to edge it is said to be at Base level and when a content appears in a separate layer above base level then it said to be at Elevated level.In Dark mode system provided background colors has lighter values for elevated levels, so that it’s easier to differentiate from the black background underneath.Foreground color remains the same.

Dynamic colors

You can either use system provided dynmaic colors or you can define your own custom dynamic colors.

Resolving system dynamic colors — System dynamic colors are resolved automatically but if have a requirement where you want to resolve it yourself then use “let dynamicColour = dynamicColour.resolvedColor(with: traitCollection)”.It checks the “userInterfaceStyle” trait, resolves and then returns an apporiate value of that color.

Note — Trait collection has new trait type now “userInterfaceStyle” which is used to determine the appearance of the view.

Custom Dynamic Colors

Custom dynamic colors can be created by following two ways

  1. Image asset catalogue

Add a new color to asset catalogue and then add another color for the dark mode.

2. Programatically

How dynamic color is resolved automatically?

UITraitcollection has a new property — “current” and dynamic color gets resolved using this property.UIKit sets the “current” traitCollection for you just before calling the methods like draw(), layoutSubViews(),updateConstraints() in UIView , updateViewConstraints(), viewWillLayoutSubviews(), viewDidLayoutSubviews() in UIViewController and containerViewWillLayoutSubViews(), containerViewDidlLayoutSubViews() in UIPresentationController.Also outside these above mentioned methods, “current” traitCollection doesn’t guaranteed to have any particular value.So if you need to resolve a dynamic color outside these methods then you need to manage it.

Following is one such case

Lower level classes — CALayer and CGColor doesn’t understand dynamic color as it is a UIKit concept.So suppose if you have a CALayer and you need to set it’s border color that takes a cgColor which can’t be dynamic. Then how to resolve this color?

let layer = Layer()

layer.borderColor = UIColor.label.cgColor

Following are the few option to resolve color outside of one of those suggested method mentioned above.

let layer = Layer()

let traitCollection = view.traitCollection

Option 1 — This is fine when you have one single dynamic color to resolve because for all the dynamic colours you needs to repeat the steps below

let resolvedColor = UIColor.label.resolvedColor(with: traitCollection)

layer.borderColor = resolvedColor.cgColor

Option 2 — This make the traitCollection as the current trait collection

traitCollection.performAsCurrent { layer.borderColor = UIColor.label.cgColor }

Option 3 — This is also safe to do on a background thread and will only affect the background thread and not the main thread.Also it’s a good idea to save the current trait collection and restore it after dynamic color is resolved in case some other code is relying on it.

let savedTraitCollection = UITraitCollection.current

UITraitCollection.current = traitCollection

layer.borderColor = UIColor.label.cgColor

UITraitCollection.current = savedTraitCollection

Note that “performAsCurrent”(option-2) does this store-restore of current traitCollection for you but in option-3 you have to do this explicitly.

Resolving Dynamic Images

Image again are resolved using the trait. e.x.

guard let image = UIImage(named: named) else { return }

let assets = image.imageAsset

guard let resolvedImage = assets?.image(with: self.traitCollection) else { return }

self.imageView.image = resolvedImage

Note — You can also use image asset to register new variation of the images at runtime. So if you are drawing images then you can register light and dark variations of that image.

Testing Dark Mode in Xcode

Following are two cases when you will want to test the application for both the display modes

  1. While designing — You can select the “interface style” option (highlighted in the screenshot below) in the Xcode to toggle between the light and dark mode for the storyboard or individual view you are working with.
Light Mode
Dark Mode

2. While running in simulator

Environment override switch — This is a switch which toggles the simulator display mode between light and dark.

Light Mode
Dark Mode

New Behaviour in iOS 13

When a view is first initialised and is not added in the trait hierarchy, UIKit makes a prediction that where that view will end up and populates the trait collection of that view right from the start based on the predicated destination.So when the view actually gets added in the view hierarchy then it inherits the traits of its parent trait environment which is the parent viewcontroller.Since the trait collection was correctly predicted here so their is no change between the predicted and inherited traits of the view. So unlike prior iOS 13, traitcollectionDidChange(:_) will only be called afterwards if any of those initial traits changes. So if you have any code inside traitcollectionDidChange(:_) that is required to perform some task before a view controller / view can be shown then it’s time to move that code outside of traitcollectionDidChange(_:) as it will not be called initially.

Few things to take a note of

“UITraitCollectionChangeLoggingEnabled” YES

This is a debug logging flag used as a launch argument. This flag will log the details of all the traitcollectionDidChange(:_) call.

Making single view .light and keeping rest of the app as .dark or vice-versa

override variable “overrideUserInterfaceStyle” present both in view and viewcontroller. i.e. self.overrideUserInterfaceStyle = .dark or self.overrideUserInterfaceStyle = .light

Making entire application light or dark irrespective of the system display mode

Add “UIUserInterfaceStyle” key with value either as Dark or Light in the “Info.plist

Status bar has now 3 styles

.darkContent, .lightContent, .default — This acts as an automatic style which will set it’s value dynamically based on the the userinterfacestyle(One of the traits) of the viewcontroller that controls the status bar appearance.

You can override the status bar style to a specific style as follows

override var preferredStatusBarStyle: UIStatusBarStyle {

return .lightContent / return .darkContent

}

UIScrollview indicator has now 3 styles

extension UIScrollView {

public enum IndicatorStyle : Int {

case `default`

case black

case white

}. . . }

You can override the scrollview indicator style to a specific style as follows

self.scrollView.indicatorStyle = .default

Activity Indicators previous styles are now deprecated i.e. .grey, .white, .whiteLarge. New dynamic styles introduced are .medium and .large.Also Activity indicator will adjust their appearance automatically for light and dark mode as they are dynamic styles but you can set a specific colour to them using it’s “color” property.

Drawing an attributed string

UITextFileld, UILabel and UITextView — All uses .label as their default text color but if you setting or drawing an attributed string for any of the above mentioned classes then make sure to specify a foreground color for the attributed text as drawing an attributed string without a foreground color is defined to yield a black color text. e.x.

let attributes = [NSAttributedString.Key: Any] = [

.font: UIFont.systemFont(ofSize: 36.0)

.foregroundColor: UIColor.label ]

iPad Apps for Mac

In some cases on the mac, UIKit will provide a slightly different version of system colors / materials to better match the style that will normally be provide by the Appkit framework.This will maintain the appearance consistency of the iPad app with the mac apps.

Note — Once you build your app against the iOS13 SDK you app will participate for dark mode by default.

At last i would highly suggest you to refer this WWDC Dark Mode video as i have skipped few things in this article from this video.Also i have created a sample application in which i have implement the concepts explained in this video.

This sample application is just for tutorial purpose and is by no means a well polished piece of code.But still if you find any incorrect information here then please let me know so that i can correct it.

For more information please refer the following link

Sample code

https://github.com/DVdroid/Dark-Mode-Demo-iOS13

Vikash Anand
Vikash Anand

No responses yet