Detecting iOS Dark Mode Change
Swift 5:
traitCollectionDidChange also gets called a few times. This is how I detect DarkMode runtime change and setColors().
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
setColors()
}
In setColors() func I update the colors.
Detecting current colorScheme:
extension UIViewController {
var isDarkMode: Bool {
if #available(iOS 13.0, *) {
return self.traitCollection.userInterfaceStyle == .dark
}
else {
return false
}
}
}
I have colors defined like this (for iOS < 13):
enum ColorCompatibility {
static var myOlderiOSCompatibleColorName: UIColor {
if UIViewController().isDarkMode {
return UIColor(red: 33, green: 35, blue: 37, alpha: 0.85)
}
else {
return UIColor(hexString: "#F3F3F3", alpha: 0.85)
}
}
}
Example:
private func setColors() {
myView.backgroundColor = ColorCompatibility.myOlderiOSCompatibleColorName
}
Also you might need to call setColors in ViewDidLoad/Will/DidAppear depending on your case like this:
viewDidLoad() {
...
setColors()
...
}
For iOS11+ you could use "named Colors", defined in Assets and much easier to use in IB.
Cheers
Detecting Change in iOS Dark Mode
If you'd like to understand how to implement dark mode in iOS 13 I would suggest you watch this wwdc video. However, if you just want a quick understanding of when the methods are called, here is a summary:
layoutSubviews()
: The layoutSubviews method is for UIView
subclasses. You can override this in your own UIView
subclasses like so:
class CustomCollectionView: UICollectionView {
override func layoutSubviews() {
super.layoutSubviews()
// your implementation here
}
}
traitCollectionDidChange()
: The traitCollectionDidChange method can be overrided for UIViewController
subclasses. You can override this in your own UIViewController
subclasses like so:
class CustomViewController: UIViewController {
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
// your implementation here.
}
}
If you want to specifically know when the theme has changed then your implementation of traitCollectionDidChange
would look something like:
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if #available(iOS 13, *), traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
// handle theme change here.
}
}
iOS Dark Mode trait detection
if #available(iOS 13.0, *) {
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection){
if UIScreen.main.traitCollection.userInterfaceStyle == .dark {
//Do DARK mode stuff
}
}
}
We finally got this working by checking the value of UIScreen.main.traitCollection.userInterfaceStyle
if it detects a differentColorAppearance and just simply acting on that mode's current interface style and this seems to work well.
How to detect Light\Dark mode change in iOS 13?
SwiftUI
With a simple environment variable on the \.colorScheme
key:
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
Text(colorScheme == .dark ? "Its Dark" : "Its. not dark! (Light)")
}
}
UIKit
As it described in WWDC 2019 - Session 214 around 23:30.
As I expected, this function is getting called a lot including when colors changing. Along side with many other functions for ViewController
and presentationController
. But there is some especial function designed for that has a similar signature in all View
representers.
Take a look at this image from that session:
Gray: Calling but not good for my issue, Green: Designed for this
So I should call it and check it inside this function:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
dropShadowIfNeeded()
}
}
This will guarantee to be called just once per change.
if you are only looking for the initial state of the style, check out this answer here
How can I check whether dark mode is enabled in iOS/iPadOS?
SwiftUI
With the \.colorScheme
key of an Environment
variable:
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
Text(colorScheme == .dark ? "In dark mode" : "In light mode")
}
}
Also, it automatically updates on the change of the environment color scheme.
UIKit
To check the current, all object those conform to UITraitEnvironment
protocol, including all UIView
subclasses and all UIViewConttroller
subclasses have access to the current style:
myUIView.traitCollection.userInterfaceStyle == .dark
myUIViewController.traitCollection.userInterfaceStyle == .dark
To detect live changes of the style, here is the full detailed answer
How do I detect dark/light mode changes in my AppDelegate in order to reset global tintColor?
- Define the color for light and dark appearance in the Asset Catalog
- Set the tint color in
AppDelegate
with theUIColor(named:
API. The color will change automatically.
How to check for Dark Mode in iOS?
UIKit has had UITraitCollection for a while now. Since iOS 9 you could use UITraitCollection to see whether the device supports 3D Touch (a sad conversation for another day)
In iOS 12, UITraitCollection got a new property: var userInterfaceStyle: UIUserInterfaceStyle
which supports three cases: light
, dark
, and unspecified
Since UIViewController inherits UITraitEnvironment, you have access to the ViewController's traitCollection
. This stores userInterfaceStyle
.
UITraitEnviroment also has some nifty protocol stubs that help your code interpret when state changes happen (so when a user switches from the Dark side to the Light side or visa versa). Here's a nice coding example for you:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if self.traitCollection.userInterfaceStyle == .dark {
// User Interface is Dark
} else {
// User Interface is Light
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// Trait collection has already changed
}
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
// Trait collection will change. Use this one so you know what the state is changing to.
}
}
iOS 14 Widget Detect System Theme Change
Although Widget views are static, you may still detect @Environment(\.colorScheme)
.
Here is a simple demo:
struct WidgetEntryView: View {
@Environment(\.colorScheme) var colorScheme
var entry: Provider.Entry
var bgColor: some View {
colorScheme == .dark ? Color.red : Color.orange
}
var body: some View {
ZStack {
bgColor
Text(entry.date, style: .time)
}
}
}
Note that when the system color scheme changes:
- only your View is redrawn, the
getTimeline
function is not called again - the default colors change automatically when the system color scheme changes
Here is a GitHub repository with different Widget examples including the Environment Widget.
Related Topics
How to Create a Hud on Top of My Scenekit.Scene
Swift Uifont Ibinspectable - Is It Possible
How to Draw a Scnnode Always in Front of Others
How to Retrieve the Type of an Object in Swift
In Swiftui How to Set the Environment Variable of Editmode in an Xcodepreview
Handling Network Error in Combination with Binding to Tableview (Moya, Rxswift, Rxcocoa)
Xcode 10 Beta 5 - Clang: Error: Linker Command Failed with Exit Code 1
How to Disable the Show Tab Bar Menu Option in Swiftui
How to Play Sound with Avaudiopcmbuffer
How to Position Child Skspritenodes Inside Their Parents
How to Get the Edited Image from Uiimagepickercontroller in Swift
Segue Not Getting Selected Row Number
Ios9: Using Dynamic Framework with Simulator and Device
Trying to Use Keychainitemwrapper by Apple "Translated" to Swift
Sliding One Swiftui View Out from Underneath Another
How to Rearrange Views in Swiftui Zstack by Dragging