-
Notifications
You must be signed in to change notification settings - Fork 1.4k
使用 QMUITheme 实现换肤并适配 iOS 13 Dark Mode
iOS 13 系统新增了 Dark Mode,需要项目自行适配,系统提供的适配方案简述如下:
-
UIView 层面
- 对于
UIColor
,使用UIColor (DynamicColors)
里提供的新 API 去初始化颜色,例如+[UIColor colorWithDynamicProvider:]
,或者用 Xcode 11 在 Assets 里为每个颜色创建一个 Color Set 然后在 Appearances 里选择带有 Dark 的选项来生成一个动态颜色,代码里通过+[UIColor colorNamed:]
来获取颜色。 - 对于
UIImage
,使用 Xcode 11 在 Assets 里的 Appearances 选择带有 Dark 的选项来创建一个动态的图片。 - 对于
UIVisualEffect
,使用 iOS 10 之后的UIBlurEffectStyle
(例如UIBlurEffectStyleRegular
),系统会自动切换 Light/Dark 样式。 - 其他内容可在
traitCollectionDidChange:
里根据self.traitCollection.userInterfaceStyle
的值来判断当前的主题。
- 对于
-
UIViewController 层面,通过在
traitCollectionDidChange:
里根据self.traitCollection.userInterfaceStyle
的值来判断当前的主题。
-
对于
UIColor
,系统有 API 生成动态颜色,但CGColor
却没有,所以使用到CGColor
的地方都需要重写traitCollectionDidChange:
在里面重新设置一遍,无法保持代码风格的一致性。 -
在 App 回到后台的时候,系统会分别使用 Light Mode 和 Dark Mode 为 App 当前界面截两张图,从而保证你切换了主题后再唤醒 App,不会看到颜色跳变的过程,保证了体验,但由于
CALayer
带有隐式动画,在截图时UIView
已经渲染完新样式了,CALayer
还是旧的,从而表现出明显的 UI 问题(注意下图最终唤醒 App 后,色块(CALayer)能看到从白变黑的过程,而界面上其他地方的颜色是看不到变化过程的)。 -
对于
UIColor
、UIImage
这些对象,如果你将其用于UIView.backgroundColor
、UIView.tintColor
这种系统原生自带的属性,当设备主题发生切换时,UIView 会自动去刷新这些值(因为它知道有这些值需要刷新),但如果你是一个自定义 View 里的自定义属性(例如QMUIGridView.separatorColor
),系统并不知道你总共有哪些属性需要更新,所以你依然需要借助traitCollectionDidChange:
来重新设置一遍,导致 UI 代码需要分散到多个地方,不好维护。 -
从开发者的角度,让一个项目兼容 iOS 13 Dark Mode,和让项目在所有 iOS 版本下都能支持 Dark Mode,这两者的工作量相差不大,都是要对已有的 UI 代码做大量修改。如果要兼容 iOS 13 Dark Mode,最大的收益肯定是同时支持所有 iOS 版本,然而系统提供的方案并不考虑 iOS 12 及以下版本的实现。
基于以上状况,我们设计了 QMUITheme 组件,它解决的问题是:
-
支持全 iOS 版本的换肤,可设置多个主题。
-
兼容 iOS 13 Dark Mode,可将 Dark Mode 映射为 App 中的某一个主题,对业务而言只需要关心业务主题,不需要关心设备当前是否开启了 iOS 13 Dark Mode。
-
支持
UIColor
、CGColor
、UIImage
、UIVisualEffect
的动态化,在 View 初始化的时候直接使用这些动态对象即可,不需要强制写在某些 updateXxx、xxxDidChange 的方法里,以保持最优雅的编码风格。 -
支持 UIKit 自带的 View 组件和业务自定义 View 组件里与颜色、图片相关属性的自动刷新。
下面以 QMUI Demo 为例,讲解 QMUITheme 组件的使用。QMUI Demo 拥有5个主题,其中“Dark”主题对应 iOS 13 Dark Mode。在 iOS 13 下,每次 QMUI Demo 启动时都会根据当前设备是否开启了 Dark Mode,来强制将 QMUI Demo 的主题设置为“Default” 或 “Dark”。
-
QMUIThemeManager
这是全局的主题管理器,可以注册、移除主题,切换当前主题,在 iOS 13 下负责监听系统 Dark Mode 的切换,并将其映射为已注册的某个业务主题。
-
主题(theme)
对项目而言,主题可以是任意的对象类型,存在多个主题时,不同主题也可以是不相同的类型,甚至可以是一个无意义仅占位用的
NSNull
。一个主题对象通常用于存储全局的颜色、图片等信息。在 QMUI Demo 里,每个主题对应一个 QMUI 配置表,也即NSObject<QDThemeProtocol> *
类型。对
QMUIThemeManager
而言,一个主题对应一个唯一的identifier
。与主题类似,identifier 对类型也没有要求,只需要支持NSCopying
协议即可,所以可以用NSString
、NSNumber
等常见类型,怎么方便怎么来。 -
动态对象 换肤的直接操作对象通常只有
UIColor
(包含CGColor
)、UIImage
、UIVisualEffect
这三种,在以前,当你拿到一个UIColor
对象,它对应什么实体的色值,是确定的,但在 iOS 13 或 QMUITheme 体系下,同一个 color 对象在不同的主题下可能会展示出不同的色值,于是这种对象我们称之为“动态对象”。动态对象均需要以特定的方式来创建,例如: