app doesn't update and traitCollectionDidChange doesn't fire when user changes appearance in settings
Asked Answered
H

1

6

In my app I allow the user to override the appearance that is set on the device. Here's the class that handles updating the UI if they want to override the system-wide appearance:

class UserInterfaceStyleController {

init() {
    self.handleUserInterfaceStyleChange()
    NotificationCenter.default.addObserver(self, selector: #selector(self.handleUserInterfaceStyleChange), name: Notification.Name(OMGNotification.changeBizzaroMode.rawValue), object: nil)
}

@objc func handleUserInterfaceStyleChange() {
    if #available(iOS 13.0, *) {
        let windows = UIApplication.shared.windows
        for window in windows {
            if UIScreen.main.traitCollection.userInterfaceStyle == .light {
                if UserDefaults.standard.bizzaroMode {
                    window.overrideUserInterfaceStyle = .dark
                } else {
                    window.overrideUserInterfaceStyle = .light
                }
            }
            if UIScreen.main.traitCollection.userInterfaceStyle == .dark {
                if UserDefaults.standard.bizzaroMode {
                    window.overrideUserInterfaceStyle = .light
                } else {
                    window.overrideUserInterfaceStyle = .dark
                }
            }
            window.subviews.forEach({ view in
                view.layoutIfNeeded()
            })
        }
    }
}

This works - the user flips the switch and the app changes the userInterfaceStyle. The problem is the app doesn't change appearance automatically when the user changes the system-wide appearance (set light or dark mode in settings), ONLY when they set it manually.

So I'm assuming I need to do some work when the user changes it system-wide, and traitCollectionDidChange seems to be what I need to use. Problem is it doesn't fire when the user changes the appearance in settings, ONLY when it's manually set in my app. I've got this code in a viewController to test that:

class ViewController: UIViewController, UIGestureRecognizerDelegate {

    // Lots of stuff

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        print("We have a style mode change")
    }

    // Lots more stuff
}

When I manually override, it prints, "We have a style mode change". When I go into settings and switch the system-wide appearance traitCollectionDidChange doesn't fire.

I swear I had this working fine at one point, but I've been trying to fix some weird issues for a while now when I use overrideUserInterfaceStyle and have burned through a lot of code changes.

I think I'm missing something obvious. Before I started allowing the user to override, the app switched automatically in the background when the appearance was changed system-wide with no code at all. Now it doesn't and traitCollectionDidChange doesn't fire. What am I missing or doing wrong? Happy to provide more code if it could help.

Thanks!

Hypoploid answered 4/3, 2020 at 18:55 Comment(0)
M
10

The problem is that you override the user interface style, which causes any system changes not to become propagated to your window. You need to set overrideUserInterfaceStyle to .unspecified, otherwise the system won't call the traitCollectionDidChange methods in your view stack on change.

In our apps, we offer the user three options: automatic, light and dark, where each option just sets overrideUserInterfaceStyle to the appropriate option.

By the way, you don't need to call layoutIfNeeded() on all your subviews—setting overrideUserInterfaceStyle triggers a redraw automatically.

Marilee answered 5/3, 2020 at 8:22 Comment(1)
Most of my views automatically redraw, but for some reason I have some that don't and I have to manually do it. I haven't quite figured out what causes it, it might have something to do with not being onscreen plus something else I haven't quite determined.Hypoploid

© 2022 - 2024 — McMap. All rights reserved.