iOS 深色模式与语义化颜色
iOS 深色模式与语义化颜色
深色模式涉及的不只是背景色。文字层级、分隔线、图片、模糊材质、系统栏和第三方内容都可能在外观切换后失去对比度。若页面到处保存具体颜色,后续主题调整会非常困难。使用语义化颜色和资源变体,可以让组件描述用途,由主题决定实际表现。
一、优先使用系统语义颜色
系统提供的标签、背景和分组背景颜色会随外观变化,并适配一定的可访问性设置。
final class InfoCard: UIView {
private let titleLabel = UILabel()
func setupAppearance() {
backgroundColor = .secondarySystemBackground
titleLabel.textColor = .label
layer.borderColor = UIColor.separator.cgColor
}
}
品牌场景需要自定义颜色时,也应使用语义名称而不是描述色值。
二、在资源中提供外观变体
颜色资源可以为浅色和深色外观提供不同值:
extension UIColor {
static let brandPrimary = UIColor(named: "BrandPrimary")!
static let contentBackground = UIColor(named: "ContentBackground")!
}
项目已验证为必备资源时可以按契约读取,不需要为每个颜色添加随意默认值。资源缺失应在开发测试阶段暴露。
三、CGColor 不会自动动态更新
把动态 UIColor 转为 CGColor 后,图层保存的是当前解析结果。外观变化时需要重新赋值。
override func traitCollectionDidChange(
_ previousTraitCollection: UITraitCollection?
) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.hasDifferentColorAppearance(
comparedTo: previousTraitCollection
) {
layer.borderColor = UIColor.separator.cgColor
chartView.updateTheme()
}
}
相关判断能力需要符合项目最低系统版本,旧项目应使用已有兼容写法。
四、图片需要独立检查
模板图标可以通过 tint 使用动态颜色。照片通常不需要变化,但插画、阴影和带透明边缘的图片可能需要深色变体。
不要对所有图片简单反色。品牌标识、人物照片和业务图表反色后会改变含义。资源目录中的外观变体更适合设计明确的替换。
五、状态颜色仍要保持语义
成功、警告、错误和涨跌颜色需要在两种背景下都清晰。颜色不能成为唯一信息来源,还应配合文字或符号。
禁用状态不要只依赖整体透明度,因为叠加在不同背景上结果不可控。使用主题中定义的禁用前景与背景更稳定。
六、处理模糊与材质
系统材质会根据当前外观调整,但自定义半透明颜色可能与底层内容产生不可预测的对比度。弹窗、导航栏和浮层应在真实页面背景上检查,而不是只看独立色板。
复杂透明叠层还会增加渲染成本,性能敏感列表中应谨慎使用。
七、同步系统界面
状态栏图标样式、导航栏、标签栏、键盘和系统弹窗应与页面外观一致。自定义容器需要把外观选择传递给子控制器,不能只改根视图背景。
如果某个业务页面强制固定外观,应明确范围,并验证弹出的控制器不会突然切换到另一主题。
八、混合页面约定主题
原生容器与 H5 页面可以传递“浅色、深色、跟随系统”等语义状态,由页面内部设计令牌解析。不要将大量具体色值通过桥接发送,这会让两端主题体系互相耦合。
页面尚未加载完成时也需要保存最新主题,准备后立即应用,避免首次闪烁。
九、测试动态切换
外观可能在应用运行中改变,不能只在冷启动时读取一次。测试应覆盖:
- 应用前台时切换系统外观。
- 页面已经展示的图层颜色。
- 弹窗和子控制器。
- 图片与空状态插画。
- 大字号和增强对比设置。
- 截图、分享卡片和打印内容。
视觉截图可以帮助发现遗漏,但关键文字仍要人工检查可读性。
十、避免散落判断
业务代码不应到处判断当前是否深色并选择具体颜色。主题服务、资源变体和语义令牌负责解析,业务组件只引用用途。
只有图表算法等确实需要根据外观调整多项参数时,才接收统一主题模型。
总结
iOS 深色模式应建立在语义化颜色上。优先使用系统动态色,自定义资源提供外观变体,注意图层颜色需要重新解析,并让图片、系统容器和混合页面同步变化。集中主题规则后,业务页面无需反复判断外观,也更容易保证对比度。
- 点赞
- 收藏
- 关注作者
评论(0)