Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 718
Brian M Avatar asked Mar 04 '20 18:03

Brian M


1 Answers

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.

like image 142
Frank Schlegel Avatar answered Sep 24 '22 05:09

Frank Schlegel