Detecting iOS Dark Mode Change

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:

WWDC 2019 - Session 214

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 the UIColor(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



Leave a reply



Submit