Programmatically Detect Dark Mode in Swiftui to Display Appropriate Image

Programmatically detect dark mode in SwiftUI to display appropriate Image

You can use @Environment(\.colorScheme) var colorScheme: ColorScheme in any view to get whether the device is in dark mode (.dark) or light mode (.light). Using that information, you can conditionally decide which image to show easily with a ternary operator.

For example, if you have an image named "lightImage" for light mode and "darkImage" for dark mode:

@Environment(\.colorScheme) var colorScheme: ColorScheme

var body: some View {
Button(action: {
foo()
}) {
Image(colorScheme == .light ? "lightImage" : "darkImage")
}
}

SwiftUI not detecting dark mode from setting

Turns out when you use

.preferredColorScheme(.light | .dark)

It applies that preference to the entire view rather than the element the property is being used on.

How to detect if Color is white or black in Swift

You can get the HSB brightness component from Color by first converting it to a UIColor. Here's a little extension I made to do this:

import UIKit
import SwiftUI

extension Color {
enum Brightness {
case light, medium, dark, transparent

private enum Threshold {
static let transparent: CGFloat = 0.1
static let light: CGFloat = 0.75
static let dark: CGFloat = 0.3
}

init(brightness: CGFloat, alpha: CGFloat) {
if alpha < Threshold.transparent {
self = .transparent
} else if brightness > Threshold.light {
self = .light
} else if brightness < Threshold.dark {
self = .dark
} else {
self = .medium
}
}
}

var brightness: Brightness {
var b: CGFloat = 0
var a: CGFloat = 0
let uiColor = UIColor(self)
uiColor.getHue(nil, saturation: nil, brightness: &b, alpha: &a)
return .init(brightness: b, alpha: a)
}
}

Color.white.brightness // .light
Color.gray.brightness // .medium
Color.black.brightness // .dark
Color.clear.brightness // .transparent

The thresholds are ad hoc and untested for any real purpose. The UIColor.init used here also requires iOS 14+ (https://developer.apple.com/documentation/uikit/uicolor/3550899-init).

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

Implement dark mode switch in SwiftUI App

Single View

To change the color scheme of a single view (Could be the main ContentView of the app), you can use the following modifier:

.environment(\.colorScheme, .light) // or .dark

or

.preferredColorScheme(.dark)

Also, you can apply it to the ContentView to make your entire app dark!

Assuming you didn't change the ContentView name in scene delegate or @main



Entire App (Including the UIKit parts and The SwiftUI)

First you need to access the window to change the app colorScheme that called UserInterfaceStyle in UIKit.

I used this in SceneDelegate:

private(set) static var shared: SceneDelegate?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
Self.shared = self
...
}

Then you need to bind an action to the toggle. So you need a model for it.

struct ToggleModel {
var isDark: Bool = true {
didSet {
SceneDelegate.shared?.window!.overrideUserInterfaceStyle = isDark ? .dark : .light
}
}
}

At last, you just need to toggle the switch:

struct ContentView: View {
@State var model = ToggleModel()

var body: some View {
Toggle(isOn: $model.isDark) {
Text("is Dark")
}
}
}


From the UIKit part of the app

Each UIView has access to the window, So you can use it to set the . overrideUserInterfaceStyle value to any scheme you need.

myView.window?.overrideUserInterfaceStyle = .dark

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.
}
}



Related Topics



Leave a reply



Submit