ios - iOS 13.0 - 支持深色模式并支持 iOS 11 和 12 的最佳方法
问题描述
所以我在 Apple Developer Forums 上发过帖子,但还没有得到回复。
背景:
iOS 13 引入了深色模式和一些具有预定义的浅色和深色变体的系统颜色:( https://developer.apple.com/videos/play/wwdc2019/214/ )
这些颜色可以在情节提要中直接用作命名颜色。它们也作为静态颜色添加到 UIColor 类中:(https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors)
但是,添加到 UIColor 的静态颜色在 iOS 11 和 12 的代码中不可用。这使得使用它们变得很棘手,因为对新系统颜色的所有引用都必须包含在可用性检查中:
它还提出了一个问题:在 iOS 11 和 12 上,当直接在 Storyboard 中使用时,系统颜色会解析成什么?在我们的测试中,它们似乎可以解析为 Light 变体,尽管我们还没有测试所有这些变体。
目前的做法:
这是我们倾向于的方法。我们会将所有颜色添加到我们的 Colors.xcassets 文件以支持旧 iOS 版本,并通过我们的 CustomColors Enum 执行单个版本检查和映射,以便根据 iOS 版本返回正确的 UIColor 系统颜色。一旦我们放弃对 iOS 11 和 12 的支持,我们将从 Colors.xcassets 中删除相应的颜色,因为我们将只使用系统颜色。我们还将重构我们所有的故事板以使用新的系统颜色。
这种方法的缺点是:
- 如果我们想在我们放弃对 iOS 11 和 12(UIColor.label、UIColor.systemBackground 等)的支持后直接在我们的代码中使用系统颜色,那么摆脱所有枚举引用可能是一个相当大的重构
- 因为我们将在情节提要中使用系统颜色,所以我们必须确保我们的 Colors.xcassets 等效项使用相同的颜色代码
- 此错误:(UIColor(named:) 在 iOS 11.0-11.2 上始终返回 nil)-如果未修复,则此方法不可用(编辑:此错误已在 XCode 11 GM 种子 2 11A420a 中修复)
- 与所有资产目录一样,使用魔术字符串访问目录中的项目很容易让开发人员犯错并得到 nil 而不是资产(本例中的颜色)。如果我们不测试每个屏幕,这可能会导致难以识别的错误,从而迫使我们编写 crashIfAllColorsNotDefined() 方法。使用枚举确实可以减轻这种风险,因为魔术字符串只在一个地方存储/使用。
其他方法:(如何使用我的应用程序中使用的自定义颜色轻松支持明暗模式?)
问题:
有哪些其他方法可以通过使用新的系统颜色来支持 iOS 13 的暗模式,同时仍支持 iOS 11 和 12?在旧版 iOS 上使用 Storyboard 中的新系统颜色是否安全?
解决方案
Enum 和 UIColor Extension 的组合是最终的选择。自定义颜色有两个“部分”——应用的特殊颜色和重复的苹果颜色。
Apple 发布的一些新颜色仅在 iOS13 或更高版本中可用(systemBackground、opaqueSeparator、secondaryLabel 等)。如果您想立即使用这些,则必须将它们创建为自定义颜色。这是一个令人担忧的问题,因为它会增加未来的技术债务,因为一旦 iOS13 成为您支持的最低版本,就必须重构这些颜色。这在 Storyboard 中尤其难以重构。
此解决方案的设置方式,可以轻松修改 UIColors 扩展以在稍后阶段返回官方苹果颜色。您应该只以编程方式设置重复的苹果颜色 - 不要直接在情节提要中使用它们。
在代码中:
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: $0.rawValue) != nil else {
Logger.log("Custom Colors - Color not defined: " + $0.rawValue)
fatalError()
}
}
}
}
在故事板中:
直接选择自定义颜色,除了重复的苹果颜色。
推荐阅读
- php - 从目录中取消链接图像
- asp.net-core - AspNetZero .NetCore + Angular 项目 - api 版本控制
- javascript - 刷新时如何保存页面状态?(HTML,JS)
- java - Java - 随机选择对象
- python - 列出在 Python 中滚动六面骰子的每次迭代
- git - 在现有分支上创建另一个分支
- git - Git - 多环境开发
- windows - 如何使用批处理文件在 Windows 10 中删除具有特定名称的文件?
- java - Azure 应用配置功能管理
- java - Spring boot 或 maven 损坏二进制文件,仅在生产中