swiftuiuikitios-darkmodeuitraitcollection

How to detect Light\Dark mode change in iOS 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?


Solution

  • 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