How to detect Light\Dark mode change in iOS 13?
Asked Answered
D

3

13

Some of the UI setups not working automatically with the Dark/Light mode change as the UIColor. For example shadow in layer. As I need to remove and drop shadow in dark and light mode, I need somewhere to put updateShadowIfNeeded() function. I know how to detect what is the mode currently:

func dropShadowIfNeeded() {
    switch traitCollection.userInterfaceStyle {
    case .dark: removeShadow()
    case .light: dropShadowIfNotDroppedYet()
    default: assertionFailure("Unknown userInterfaceStyle")
    }
}

Now I put the function inside the layoutSubviews, since it gets called every time appearance change:

override func layoutSubviews() {
    super.layoutSubviews()
    dropShadowIfNeeded()
}

But this function is getting called A LOT. What is the proper function to trigger only if userInterfaceStyle changed?

Dentil answered 19/9, 2019 at 18:16 Comment(1)
Does this answer your question? How can I check whether dark mode is enabled in iOS/iPadOS?Interregnum
D
29

SwiftUI

With a simple environment variable on the \.colorScheme key:

struct ContentView: View {
    @Environment(\.colorScheme) private 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

Dentil answered 19/9, 2019 at 18:36 Comment(4)
The gray colors do not mean "not good" and the green colors do not mean "designed for this". The green highlight just indicates that the speaker was talking about those methods. (If you back up a few seconds you'll see the other methods highlighted in green too.) I'm sorry if we gave you the wrong impression.Supposed
Even if this in theory is guaranteed to be called once for change, when putting the app in background sometimes it gets called twice (switch from light to dark and the other way round). See this SO: #59140257Inscription
The trait collection changes and the method is calling, but that if statement prevents it from the duplicate call @InscriptionDentil
Unfortunately, I have experienced duplicated calls even with that if in place @MojtabaHosseini. Basically what happens sometimes when you put the app in background is that first the colour appearance switches from light to dark and then the other way round. Take a look at the SO I posted in the previous comment.Inscription
T
2

I think this should get called significantly less often, plus the guard makes sure you only react to user interface style changes:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)

    guard previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle else {
        return
    }
    dropShadowIfNeeded()
}
Testudo answered 19/9, 2019 at 18:36 Comment(2)
There is an API especially for color change traitCollection.hasDifferentColorAppearance. But thanks in advanced.Dentil
And don't forget the override keyword and then, call super's method to prevent unexpected not working situations.Dentil
S
0

With RxSwift and ObjectiveC runtime, you can achieve it without inheritance

here is the encapsulated version:

import UIKit
import RxSwift
import RxCocoa

enum SystemTheme {
    static func get(on view: UIView) -> UIUserInterfaceStyle {
        view.traitCollection.userInterfaceStyle
    }

    static func observe(on view: UIView) -> Observable<UIUserInterfaceStyle> {
        view.rx.methodInvoked(#selector(UIView.traitCollectionDidChange(_:)))
            .map { _ in SystemTheme.get(on: view) }
            .distinctUntilChanged()
    }
}
Spousal answered 25/9, 2019 at 4:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.