深色模式适配一次搞定:Colorful如何用traitCollectionDidChange优雅切换明暗主题
【免费下载链接】Color-Picker-for-iOSColorful: iOS color picker built with Swift.项目地址: https://gitcode.com/gh_mirrors/co/Color-Picker-for-iOS
Colorful 是一款用 Swift 编写的 iOS 取色器(Color Picker)库,它在深色模式适配上做了一个非常值得学习的示范:借助系统的traitCollectionDidChange回调,配合动态颜色,一次搞定明暗主题切换,无需手动监听通知、也不会出现"切一次主题才生效一次"的尴尬。🌗
为什么深色模式适配总是做不干净?
很多 iOS 项目遇到深色模式时,最常见的翻车姿势有这几种:
- 硬编码颜色:
UIColor(white: 0.89, alpha: 1)写死了浅色值,深色下变成一块刺眼的"白斑"; - 手动监听通知:到处监听
UIColorAppearance或自定义通知,注册时机稍晚就错过首次切换; - 切换后不刷新:Layer 上的颜色是"快照",
CGColor不会随主题自动更新,切完主题界面还是旧样子。
Colorful 的做法是:能用系统动态颜色就用,必须刷新的 Layer 属性,交给每个视图自己的traitCollectionDidChange统一处理。
Colorful 的深色模式适配思路:动态颜色打底
Colorful 的核心 UI 由四个部分组成,源码都在Colorful/Classes/目录下:
| 组件 | 文件 | 职责 |
|---|---|---|
| 色相/饱和度圆盘 | ColorMapView.swift | 绘制颜色映射图,外圈描边需随主题变化 |
| 明度滑条 | BrightnessSlider.swift | 滚轮式亮度选择,边框与阴影渐变随主题变化 |
| 明度游标 | BrightnessCursor.swift | 显示当前色值 HEX |
| 圆盘游标 | ColorMapCursor.swift | 显示当前选中色 |
在边框这类装饰性颜色上,Colorful 优先使用 iOS 13+ 的动态系统颜色,它们会自动跟随外观变化:
// Colorful/Classes/ColorMapView.swift 中的 updateStrokeColor() let separatorColor: UIColor if #available(iOS 13.0, *) { separatorColor = UIColor.tertiarySystemGroupedBackground } else { separatorColor = #colorLiteral(red: 0.894, green: 0.894, blue: 0.894, alpha: 1) }明度滑条上下两端的阴影渐变色,则使用UIColor.systemBackground打底,低版本回退为白色:
// Colorful/Classes/BrightnessSlider.swift 中的 updateShadowColor() if #available(iOS 13.0, *) { bgColor = UIColor.systemBackground } else { bgColor = UIColor.white }#available的版本判断,让库在 iOS 10 老系统上也能优雅降级——这一点在 Colorful.podspec 中也能印证,它的部署目标是 iOS 10.0。✅
关键一步:用 traitCollectionDidChange 拦截主题切换
系统动态颜色能解决 UILabel、UIView 背景色的自动更新,但CALayer 上的strokeColor、colors这些 CGColor 是静态快照,不会自己变。Colorful 的解法非常克制——在需要刷新的视图里重写一个方法:
// Colorful/Classes/ColorMapView.swift (L101-L104) override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) updateStrokeColor() }BrightnessSlider里是同样的模式,只是多刷新了阴影渐变色:
// Colorful/Classes/BrightnessSlider.swift (L102-L106) override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) updateBorderColor() updateShadowColor() }这个方法就是 UIKit 在用户切换明暗主题(以及尺寸类、对比度等特征变化)时自动回调的钩子。用它的好处:
- 零手动监听:不用注册
NSNotification,不存在注册晚、泄漏、忘记注销的问题; - 刷新范围最小化:谁需要变色,谁自己重写,
ColorMapView只刷描边,BrightnessSlider只刷边框和阴影,互不干扰; - 时机精准:回调时视图已经处于新外观上下文里,此时读取
UIColor.tertiarySystemGroupedBackground拿到的就是深色值。
一个容易被忽略的细节:切换瞬间禁用动画
注意 Colorful 在更新 Layer 颜色时都包了一层:
CATransaction.begin() CATransaction.setDisableActions(true) backgounrdLayer.strokeColor = separatorColor.cgColor CATransaction.commit()CACurrentTransaction默认会给属性变化加 0.25 秒隐式动画。主题切换的瞬间如果让描边颜色"慢慢渐变过去",视觉上会显得拖沓。setDisableActions(true)让颜色瞬时完成切换,和系统整体外观翻转同步——这个细节让深色模式的切换体验浑然一体。✨
快速上手:把 Colorful 集成到你的项目
Colorful 支持 CocoaPods,安装只需两步:
# Podfile platform :ios, '10.0' pod "Colorful", "~> 3.0"$ pod install也可以直接克隆仓库查看示例工程(Example/目录下有完整的 Demo):
git clone https://link.gitcode.com/i/4f11983383d604761f793468ae18d915基本用法如下,切换颜色时会收到.valueChanged事件:
let colorPicker = ColorPicker(frame: ...) colorPicker.addTarget(self, action: #selector(colorChanged), for: .valueChanged) colorPicker.set(color: .red, colorSpace: .extendedSRGB) view.addSubview(colorPicker)ColorPicker本体定义在Colorful/Classes/ColorPicker.swift,它对深色模式是"透明"的——外观切换完全由内部子视图各自处理,调用方什么都不用做。🎨
照猫画虎:自己项目里适配深色模式的 3 条清单
把 Colorful 的做法提炼出来,就是三步:
- 装饰色优先用系统动态颜色(如
tertiarySystemGroupedBackground、systemBackground),并给老系统留#colorLiteral回退; - 给每个持有 Layer 的视图重写
traitCollectionDidChange,记得先调super,只刷新自己负责的属性; - 用
CATransaction.setDisableActions(true)包裹颜色更新,让切换瞬时生效。
深色模式适配的本质不是"多写一遍深色 UI",而是让颜色随上下文自动流动。Colorful 用不到 20 行代码就完成了这件事,干净、克制,值得作为你的参照模板。
【免费下载链接】Color-Picker-for-iOSColorful: iOS color picker built with Swift.项目地址: https://gitcode.com/gh_mirrors/co/Color-Picker-for-iOS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考