swiftios-darkmodebackground-foregrounduitraitcollection

How can you allow for a user changing to dark or light mode with the app open on iOS?


I have updated views in my app to support dark mode by adding

        if #available(iOS 12.0, *) {
           if self.traitCollection.userInterfaceStyle == .dark {
               //Adapt to dark Bg
           } else {
                //Adapt to light Bg
            }
        }

Then, to allow for the case where the user backgrounds the app and returns to it after switching mode, I attach an observer in my viewDidLoad

        if #available(iOS 12.0, *) {
            NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
        } else {
            // Fallback on earlier versions
        }

which triggers the function

    @available(iOS 12.0, *)
    @objc func willEnterForeground() {
        if self.traitCollection.userInterfaceStyle == .dark {
            print("App moving to foreground - dark")
            //Adapt to dark Bg
        } else {
            print("App moving to foreground - light")
            //Adapt to light Bg
        }
    }

However, self.traitCollection.userInterfaceStyle still gives the old value so a full reload of the view is required to produce the desired update to the interface. Using UIApplication.didBecomeActiveNotification instead makes no difference.


Solution

  • You don't need all those messy if statements! Just add your colours to your Asset Catalogue and the right one will automatically be selected. This is similar to how you can add x1, x2 and x3 images, and the right one will be selected.

    Go to the Asset Catalogue and at the bottom left, click on the plus button, select "New Color Set":

    enter image description here

    Give the colour a name, and in the property inspector, set "Appearance" to "Any, Dark":

    enter image description here

    Choose a colour for each appearance:

    enter image description here

    Finally, use the UIColor(named:) initialiser to initialise the colours and they will automatically change when the device's dark mode settings change:

    someView.backgroundColor = UIColor(named: "myColor")
    

    EDIT:

    If the colours are only known at runtime, you can use the init(dynamicProvider:) initialiser (iOS 13 only though):

    someView.backgroundColor = UIColor {
        traits in
        if traits.userInterfaceStyle == .dark {
            // return color for dark mode
        } else {
            // return color for light mode
        }
    }