1. 引言
随着 iOS 13 引入 Dark Mode(深色模式),应用需要同时适配浅色与深色两种外观,以提供一致且舒适的用户体验。本文记录我在实际项目中适配 Dark Mode 的完整思路与踩坑笔记,涵盖系统机制、颜色管理、图片处理、第三方库兼容以及常见问题排查。
2. 系统机制
2.1 外观模式
iOS 通过 UITraitCollection.userInterfaceStyle 表示当前外观,取值包括:
.light:浅色模式.dark:深色模式.unspecified:未指定
系统切换外观时,会触发 traitCollectionDidChange 回调,视图控制器可在此感知变化并刷新界面。
2.2 动态颜色
系统提供动态颜色(Dynamic Color),同一颜色在不同外观下自动呈现不同值。例如:
swift
let dynamicColor = UIColor { traitCollection in
return traitCollection.userInterfaceStyle == .dark ? .black : .white
}
也可以使用系统提供的语义颜色,如 UIColor.systemBackground、UIColor.label 等,它们会自动适配两种模式。
3. 颜色管理
3.1 使用语义颜色
优先使用系统语义颜色,避免硬编码色值:
systemBackground:页面背景secondarySystemBackground:次级背景label:主文本secondaryLabel:次级文本separator:分割线
3.2 自定义动态颜色
在 Asset Catalog 中为颜色开启 Appearances,分别设置 Light 与 Dark 色值,代码中直接引用即可自动适配。
3.3 避免硬编码
不要在代码中直接写死 UIColor.white 或 UIColor.black,应统一通过动态颜色或语义颜色管理,否则深色模式下会出现刺眼的白色背景。
4. 图片适配
4.1 Asset Catalog 多外观图片
在 Asset Catalog 中为图片开启 Appearances,分别提供 Light 与 Dark 两套资源,系统会自动选择对应外观的图片。
4.2 动态图片渲染
对于需要根据外观动态生成的图片,可在代码中根据 traitCollection.userInterfaceStyle 选择不同资源:
swift
let image = traitCollection.userInterfaceStyle == .dark ? UIImage(named: "icon_dark") : UIImage(named: "icon_light")
4.3 模板图片
使用 withRenderingMode(.alwaysTemplate) 的模板图片,配合 tintColor 使用动态颜色,可自动适配两种外观。
5. 第三方库兼容
5.1 检查库的适配情况
接入第三方 UI 库时,需确认其是否已适配 Dark Mode。未适配的库可能出现背景色异常、文字不可见等问题。
5.2 覆盖默认样式
对未适配的第三方组件,可通过分类或子类覆盖其默认颜色,统一替换为动态颜色。
5.3 强制外观
若某个页面需要固定使用浅色或深色,可通过 overrideUserInterfaceStyle 强制指定:
swift
overrideUserInterfaceStyle = .light
6. 常见问题排查
6.1 颜色不随外观变化
检查是否使用了硬编码颜色,或颜色未在 Asset Catalog 中开启 Appearances。
6.2 图片不切换
确认图片资源是否配置了多外观,或代码中是否根据 traitCollection 动态选择。
6.3 状态栏样式
状态栏文字颜色需根据外观调整,可在视图控制器中重写 preferredStatusBarStyle:
swift
override var preferredStatusBarStyle: UIStatusBarStyle {
return traitCollection.userInterfaceStyle == .dark ? .lightContent : .darkContent
}
6.4 键盘与弹窗
键盘和系统弹窗会自动适配外观,但自定义弹窗需手动处理背景与文字颜色。
7. 总结
Dark Mode 适配的核心在于统一使用动态颜色与语义颜色,避免硬编码;图片资源通过 Asset Catalog 多外观配置;第三方库需逐一确认兼容性。适配过程中建议在真机上反复切换外观验证,确保所有页面表现一致。
适配完成后,应用在浅色与深色模式下都能提供良好的视觉体验,提升用户满意度。