iOS 13.0 - 支持深色模式并支持 iOS 11 & 12 的最佳方法

iOS 13.0 - Best approach for supporting Dark Mode and also support iOS 11 & 12

所以我已经在 Apple 开发者论坛上发帖,但还没有得到回复。

背景:

iOS 13 引入了深色模式和许多具有预定义明暗变体的系统颜色:(https://developer.apple.com/videos/play/wwdc2019/214/)

这些颜色可以直接作为命名颜色在故事板中使用。它们也作为静态颜色添加到 UIColor class: (https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors)

但是,添加到 UIColor 的静态颜色在 iOS 11 和 12 的代码中不可用。这使得使用它们变得棘手,因为对新系统颜色的所有引用都必须包含在可用性检查中:

这也提出了一个问题:在iOS11和12上,直接在情节提要中使用系统颜色会解析成什么?在我们的测试中,它们似乎解析为 Light 变体,尽管我们还没有测试所有的变体。


当前方法:

这是我们倾向于的方法。我们会将所有颜色添加到我们的 Colors.xcassets 文件以支持旧的 iOS 版本,并通过我们的 CustomColors Enum 执行单个版本检查和映射,以便根据 [=68= 返回正确的 UIColor 系统颜色] 版本。一旦我们放弃对 iOS 11 和 12 的支持,我们将从 Colors.xcassets 中删除相应的颜色,因为我们将只使用系统颜色。 我们还将重构所有故事板以使用新的系统颜色。

这种方法的缺点是:

其他方法:()


问题:

还有哪些其他方法可以通过使用新的系统颜色来支持 iOS 13 的深色模式,同时仍然支持 iOS 11 和 12? 在旧 iOS 版本的情节提要中使用新的系统颜色是否安全?

Enum 和 UIColor Extension 的组合是最终的选择。 自定义颜色有两种 'parts' - 您应用的特殊颜色和重复的苹果颜色。

Apple 发布的一些新颜色仅在 iOS13 或更高版本中可用(systemBackground、opaqueSeparator、secondaryLabel 等)。如果您想立即使用这些颜色,则必须将它们创建为自定义颜色。这是一个问题,因为它增加了未来的技术债务,因为一旦 iOS13 成为您的最低支持版本,就必须重构这些颜色。这在情节提要中尤其难以重构。

根据此解决方案的设置方式,UIColors 扩展可以在后期轻松修改为 return 官方苹果颜色。您应该只以编程方式设置重复的苹果颜色 - 不要直接在情节提要中使用它们。

代码中:

self.backgroundColor = .red1
self.layer.borderColor = UIColor.successGreen1.cgColor

颜色枚举:

// Enum for all custom colors
private enum CustomColors : String, CaseIterable {
    case red1 = "red1"
    case red2 = "red2"
    case blue1 = "blue1"
    case blue2 = "blue2"
    case successGreen1 = "successGreen1"
    case warningOrange1 = "warningOrange1"

    //----------------------------------------------------------------------
    // MARK: - Apple colors
    //----------------------------------------------------------------------

    // Duplicates for new apple colors only available in iOS 13
    case opaqueSeparator = "customOpaqueSeparator"
    case systemBackground = "customSystemBackground"
    case systemGroupedBackground = "customSystemGroupedBackground"
    case secondarySystemGroupedBackground = "customSecondarySystemGroupedBackground"
    case secondaryLabel = "customSecondaryLabel"
    case systemGray2 = "customSystemGray2"
}

UIColor 扩展:

// Extension on UIColor for all custom (and unsupported) colors available
extension UIColor {

    //----------------------------------------------------------------------
    // MARK: - Apple colors with #available(iOS 13.0, *) check
    //----------------------------------------------------------------------

    // These can all be removed when iOS13 becomes your minimum supported platform.
    // Or just return the correct apple-defined color instead.

    /// Opaque Seperator color
    static var customOpaqueSeparator: UIColor {
        if #available(iOS 13.0, *) {
            return UIColor.opaqueSeparator
        } else {
            return UIColor(named: CustomColors.opaqueSeparator.rawValue)!
        }
    }

    /// System Background color
    static var customSystemBackground: UIColor {
        if #available(iOS 13.0, *) {
            return UIColor.systemBackground
        } else {
            return UIColor(named: CustomColors.systemBackground.rawValue)!
        }
    }

    /// System Grouped Background color
    static var customSystemGroupedBackground: UIColor {
        if #available(iOS 13.0, *) {
            return UIColor.systemGroupedBackground
        } else {
            return UIColor(named: CustomColors.systemGroupedBackground.rawValue)!
        }
    }

    // more

    //----------------------------------------------------------------------
    // MARK: - My App Custom Colors
    //----------------------------------------------------------------------

    /// Red 1 color
    static var red1: UIColor {
        return UIColor(named: CustomColors.red1.rawValue)!
    }

    /// Red 2 color
    static var red2: UIColor {
        return UIColor(named: CustomColors.red2.rawValue)!
    }

    /// Success Green 1 color
    static var successGreen1: UIColor {
        return UIColor(named: CustomColors.successGreen1.rawValue)!
    }

    // more

    //----------------------------------------------------------------------
    // MARK: - Crash If Not Defined check
    //----------------------------------------------------------------------

    // Call UIColor.crashIfCustomColorsNotDefined() in AppDelegate.didFinishLaunchingWithOptions. If your application 
    // has unit tests, perhaps ensure that all colors exist via unit tests instead.

    /// Iterates through CustomColors enum and check that each color exists as a named color.
    /// Crashes if any don't exist.
    /// This is done because UIColor(named:) returns an optionl. This is bad - 
    /// it means that our code could crash on a particular screen, but only at runtime. If we don't coincidently test that screen
    /// during testing phase, then customers could suffer unexpected behavior.
    static func crashIfCustomColorsNotDefined() {
        CustomColors.allCases.forEach {
           guard UIColor(named: [=12=].rawValue) != nil else {
            Logger.log("Custom Colors - Color not defined: " + [=12=].rawValue)
            fatalError()
           }
       }
    }
}

在故事板中:

直接选择自定义颜色,除了重复的苹果颜色。

Colors.xcassets: