0

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
    }
}()
Pauli
  • 343
  • 1
  • 4
  • 17

2 Answers2

1

This works for me in real time, and is very close to what you have:

static var myFloat : Float {
    let tc = UITraitCollection.current
    let mode = tc.userInterfaceStyle
    if #available(iOS 13.0, *) {
        return (mode == .light ? 1 : 0)
    }
    return 1
}

As Leo Dabus points out, the only real difference between what you're doing and what I'm doing is that I've got a computed property which is recomputed every time you fetch its value, whereas you've got a define-and-call initializer for a property that never changes after being initialized.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    I believe OP wants the float value to change automatically when the user switches to dark/light mode, like UIColor does. For that, you would have to implement `traitCollectionDidChange(_:)` and re-use the computed property. – Eilon Feb 24 '20 at 00:09
  • @Eilon It does change automatically but of course you have to _ask_ for it again. In other words, the float value is always correct for the current trait collection. What more can one ask for? Semantic colors work just the same way. – matt Feb 24 '20 at 00:26
  • 1
    @LeoDabus Very good point. What I was trying to say by "virtually identical" was that his general technique seems to me to be on the right track. I'll modify my answer to take the mystery out of it. – matt Feb 24 '20 at 02:13
  • @matt Unfortunately, this doesn't work the way I wanted. As @Eilon mentions, I need to have the float value to change automatically when the user switches dark/light mode on runtime. If you look at my first code block with the UIColor, this does exactly that. I can just use the `bw100` var, and it updates automatically, I don't need to refetch (which would require additional code implementation for checking the switch of dark / light mode) – Pauli Feb 24 '20 at 09:39
  • 1
    But that makes no sense. A color is a class. It is an object that has persistence and can change its display in real time in response to a change in light/dark mode, like a button or other view. A float is just a value used during a calculation; it exists only during the instant you fetch and use it. A number that could magically alter itself and upset all calculations on which it depends would be a monstrosity. It is up to _you_ to recalculate things when the trait collection changes, and my solution lets you do that. – matt Feb 24 '20 at 13:05
1

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.

Eilon
  • 2,698
  • 3
  • 16
  • 32