Search code examples
swiftios-darkmodeuitraitcollection

Swift: get dynamic float value for dark / light mode


I am using quite often semantic colors in the following way to have dynamic colors for dark mode and light mode. With this approach the colors will also update on runtime when the user switches dark / light mode:

public static var bw100: UIColor = {
    if #available(iOS 13, *) {
        return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in
            if UITraitCollection.userInterfaceStyle == .dark {
                // Return the color for Dark Mode
                return .black
            } else {
                // Return the color for Light Mode
                return .white
            }
        }
    } else {
        // Return a fallback color for iOS 12 and lower.
        return .white
    }
}()

Now I want to do the same with a Float value, like having a semantic float var. That means I can access a different float value for dark mode and for light mode AND the value will adapt on runtime if the user switches dark / light mode. I couldn't find a solution for this.

This does NOT work, as it does not update on runtime. The app has to be restarted after dark / light mode switch:

 public static var myFloat: Float = {
    if #available(iOS 13.0, *) {
        if UITraitCollection.current.userInterfaceStyle == .dark {
            return 0.9
        }
        else {
            return 0.1
        }
    }
    return 0.1
}()

This does also NOT work (tried similar approach to the working one above), but here I get an error Initializer init(_:) requires that (UITraitCollection) -> Float conforms to BinaryInteger

public static var myFloat: Float = {
    if #available(iOS 13, *) {
        return Float { (UITraitCollection: UITraitCollection) -> Float in
            if UITraitCollection.userInterfaceStyle == .dark {
                // Return the Float for Dark Mode
                return 0.9
            } else {
                // Return the Float for Light Mode
                return 0.1
            }
        }
    } else {
        // Return a fallback for iOS 12 and lower.
        return 0.1
    }
}()

Solution

  • You cannot achieve something identical to how UIColor works with a Float as UIColor has a special initializer that directly to interface style change. However the solution is still fairly simple, as you have to, as mentioned, listen to an interface style change by implementing traitCollectionDidChange(_:) and recompute your data by hand.
    The following code should achieve it for you:

    // ViewController.swift
    
    var myStoredFloat: Float = 1.0 {
        willSet {
            print(newValue)
        }
    }
    
    var myComputedFloat: Float {
        let tc = UITraitCollection.current
        let mode = tc.userInterfaceStyle
        if #available(iOS 13.0, *) {
            return (mode == .light ? 1 : 0)
        }
        return 1
    }
    
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        self.myStoredFloat = self.myComputedFloat
    }
    

    Of course you can get rid of the stored property completely if you don't depend on it and just use the computed property.

    *Thanks to matt for the computed property code.