iosswiftios13xcode11ios-darkmode

How to create an initializer with dynamicProvider for dark mode


Working on support for dark mode I've been able to have dynamic colors that change as you change themes using this pattern:

static var dynamicColor: UIColor = {
    if #available(iOS 13, *) {
        return UIColor { (traitCollection: UITraitCollection) -> UIColor in
            switch (traitCollection.userInterfaceStyle, traitCollection.accessibilityContrast) {
            case (.dark, .high):
                return .somethingCustom4

            case (.dark, _):
                return .somethingCustom2

            case (_, .high):
                return .somethingCustom3

            default:
                return .somethingCustom1
            }
        }

    } else {
        return .somethingCustom1
    }
}()

I'm trying to simplify the creation of UIColor between iOS 12 and 13 with support for dark mode. I came up with a simple Theme enum to encapsulate all the logic above and it just exposes 4 cases, so the above declaration simplifies into this:

static var customColor: UIColor = {
    switch UIColor.theme {
    case .light:
        return somethingCustom1

    case .dark:
        return .somethingCustom2

    case .lightHighContrast:
        return somethingCustom3

    case .darkHighContrast:
        return .somethingCustom4
    }
}()

The problem is that while before the simplification everything updated as it should, now the theme isn't getting the updated value so the colors do not change. This is how I'm declaring Theme with the init(dynamicProvider:) initializer, but it seems like UITraitCollection.current doesn't have the right value when you switch between dark/light mode:

public enum Theme {
    case light, dark, lightHighContrast, darkHighContrast

    @available(iOSApplicationExtension 13.0, *)
    init(dynamicProvider: @escaping (UITraitCollection) -> Theme) {
        self = dynamicProvider(UITraitCollection.current)
    }
}

public extension UIColor {
    static var theme: Theme {
        if #available(iOS 13, *) {
            return Theme { (traitCollection: UITraitCollection) -> Theme in
                switch (traitCollection.userInterfaceStyle, traitCollection.accessibilityContrast) {
                case (.dark, .high):
                    return .darkHighContrast

                case (.dark, _):
                    return .dark

                case (_, .high):
                    return .lightHighContrast

                default:
                    return .light
                }
            }

        } else {
            return .light
        }
    }
}

What am I doing wrong here? I think that's how that initializer is supposed to work but I haven't been able to find any other examples of this online…


Solution

  • Okay, so after a lot of messing around I'm still not sure why the original implementation doesn't work, but I did find another way of making it work! So if you're trying to simplify your implementation for support for both iOS 12 and 13, here's what I ended up with:

    public extension UIColor {
        private static func make(dynamicProvider: @escaping (Theme) -> UIColor) -> UIColor {
            guard #available(iOSApplicationExtension 13.0, *) else { return dynamicProvider(.light) }
            return UIColor { (traitCollection) -> UIColor in
                return dynamicProvider(Theme(traitCollection))
            }
        }
    
        static var customColor: UIColor {
            return .make { (theme) -> UIColor in
                switch theme {
                case .light:
                    return .somethingCustom1
    
                case .dark:
                    return .somethingCustom2
    
                case .lightHighContrast:
                    return .somethingCustom3
    
                case .darkHighContrast:
                    return .somethingCustom4
                }
            }
        }
    }
    
    public enum Theme {
        case light, dark, lightHighContrast, darkHighContrast
    
        @available(iOSApplicationExtension 13.0, *)
        init(_ traitCollection: UITraitCollection) {
            switch (traitCollection.userInterfaceStyle, traitCollection.accessibilityContrast) {
            case (.dark, .high):
                self = .darkHighContrast
    
            case (.dark, _):
                self = .dark
    
            case (_, .high):
                self = .lightHighContrast
    
            default:
                self = .light
            }
        }
    }