31

优酷暗黑模式(六):暗黑模式的技术支撑 iOS

 4 years ago
source link: https://www.infoq.cn/article/Lblg6Zk1YO1VVj9DPNbc
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、概述

Apple 是从 iOS 13 正式发布了对暗黑模式的支持。

参考文档:

iOS: https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/dark-mode/

苹果在 iOS13 以前已经对自己的部分应用加入了深色模式的支持,比如 iBooks。iBooks 切换到深夜模式后,仍然保持美观的用户界面,对于长时间盯着屏幕的用户而言降低了眼睛的疲劳度。

适配暗黑模式要面临的问题,是如何在深色和浅色模式下界面同时保持应用的视觉效果。涉及各组件的颜色适配,图标的适配,整体的观感,还需要考虑开发的工作量,适配暗黑的方式,以及对现有业务的影响等。

适配暗黑模式和应用换肤的大有区别:对于 App 的部分界面本身具备换肤的能力,切换到一个深色的皮肤并不代表适配了暗黑模式。从用户的角度来看,如果换肤的时机与系统切换暗黑模式是一致的,两者看不出明显的区别。但是对于复杂的像优酷这样大型 App,很难对所有的界面,包括弹窗,提示,H5 页面,提供换肤的能力。从这点看,暗黑模式 SDK 必须是比换肤更简便,更轻量,覆盖更全面的方案。

在优酷 iOS 的暗黑模式开发过程中,我们自行开发了一个“暗黑 SDK”。我们希望达到的目的是,业务代码只需要最少的改动就能适配暗黑模式。

1、前期实践

在开发暗黑 SDK 之前,我们对优酷 APP 中常用的组件和基本控件构建了一套标准化组件库,对于间距,字号,颜色等基础属性,从设计维度提炼出一整套通过 DesignToken 来访问的属性库。标准组件库接入暗黑 SDK 之后,使用了标准组件库的业务代码无需修改就可以直接适配暗黑模式。对于其他使用非标准组件的业务,暗黑 SDK 提供了最简化的接入方案。

2、暗黑 SDK 在 App 中的位置

App 动态颜色的维护和切换由暗黑 SDK 来完成。下图是暗黑 SDK 在 App 层次结构中的位置。

f6J3yez.png!web

1)暗黑 SDK 的初始化。暗黑 SDK 是在 App 启动时初始化的,主要完成以下工作 :

  • 暗黑模式开关的设置
  • 对各类 View 的相关方法进行注册
  • 装载预置的主题集,比如深色和浅色两种主题
  • 自定义动态颜色的初始化。

2)暗黑 SDK 的工作原理。暗黑 SDK 作为监听系统暗黑模式变更,并通知界面切换颜色以及图片的统一入口,暗黑 SDK 提供所有动态颜色 / 动态图片的 Token 接口,供业务方使用。暗黑 SDK 汇总了全部的动态颜色色值,集中维护,方便将来新增主题。为以后后台管理,下发主题提供了可能性。

当系统的暗黑模式切换时, 暗黑 SDK 监听到模式变更,开始遍历 App 所有窗口,以及窗口下的各类注册过的 View, 然后遍历 View 的注册过的属性。

如果属性的值是动态颜色或者动态图片, 则会根据当前的暗黑模式取对应的颜色或图片, 然后重新赋值。业务代码不用主动刷新页面, 也不用监听当前是不是暗黑模式, 不涉及服务端, 不需要关心当前 App 以哪一种模式运行。

3、暗黑 SDK 更新界面的工作流程:

r6Nn6rr.png!web

4、暗黑 SDK 在视图链中搜寻标记了动态属性的节点:

z2EJ3mm.png!web

5、暗黑 SDK 的开关以及支持的类型:

eUjYzey.png!web

6、和苹果官方的暗黑切换的调用过程的对比

通过苹果官方提供的接口分析,当系统的暗黑模式变化时,任何层级的 UIView 的 traitCollectionDidChange 方法都可以被调用到,说明苹果暗黑内部的实现方法必然是一种遍历所有 UIWindow,UIVIewController 及 UIView 的机制,或者类似遍历的机制。

暗黑 SDK 同样使用了遍历所有 UIWindow 的方式,寻找设置了动态颜色或动态图片的节点,来达到在 Light 和 Dark 模式之间切换的效果。

为什么采用遍历的方式?

适配暗黑模式,是不是就是用不同的颜色和图片刷新下界面就可以了?答案是否定的。

刷新界面不能解决适配暗黑的所有问题,还会带来负面影响。

因为适配暗黑模式的工作,主要是对老的业务代码进行修改。如果沿用现有代码逻辑中的刷新逻辑的话,假如刷新中包含数据请求,会导致同时触发了不必要的代码和逻辑。如果刷新导致用户当前的操作现场丢失,是不太好的用户体验。

另外,不是所有代码都存在刷新逻辑,比如一个普通的弹窗,创建的时候数据是固定的,显示后不存在刷新的必要。对于这样的控件,如果为了接入暗黑模式需要新增一个专门的刷新函数的话,对于优酷这样的大型 App 其工作量不可想象。如果存在大量这样的改动,也会带来大量测试的回归工作量。

考虑到这些特殊情况,为了达到暗黑切换的效果且不影响到业务逻辑,最直接和高效的办法是直接修改界面元素的相关属性,比如直接修改 UILabel 的 textColor。遍历整个视图层次链,看似费时费力,实则最大程度地减轻了业务方的工作量,覆盖面相对完整。

二、动态颜色的支持

mmUn63j.png!web

从最简单的案例开始:

jQ36jei.png!web

1、上图中的暗黑模式切换涉及到以下两种情况:

Bfy6n2F.png!web

1)这个蓝色的按钮在深色和浅色下没有任何变化,文字颜色是白色,背景图保持不变

2)容器的背景色,浅色模式下是白色,深色模式下是黑色,其他按钮,比如,

E3qeiuZ.png!web

在浅色模式下文字是黑色,背景是白色,在深色模式下相反。

2、解决方案:

第一种情况:在深色和浅色没有任何变化的代码,保持不变。

第二种情况:实现暗黑模式的切换,需要做如下改动:

UVFbaez.png!web

涉及到图片的案例

m6ZjA3j.png!web

1、上图中的暗黑切换涉及到以下两种情况:

1)按钮的颜色变化,上个示例已经讲过

2)弹窗的背景图的变化,浅色模式下是一张浅红色的图片,深色模式下是一种透明的图片。

解决方案:

实现暗黑模式的切换,需要做如下改动:

3AjENjy.png!web

从上面的两个例子可以看出,适配暗黑只需要将现有代码中的颜色和图片替换成带 Token 的颜色和图片即可。可以看出 Token 是暗黑切换的关键。

2、Token 的设计

77jeyun.png!web

这个表格定义了几种常见的动态颜色的 token。

比如 primaryBackground 是一个背景色的 token,对应两种颜色,在浅色模式下是#FFFFFF 白色,在深色模式下是#16161A 浅白色。

带 token 的动态颜色可以适应暗黑模式的变化。

3、全局暗黑模式开关

考虑到暗黑模式的支持的早期阶段,可能存在解决方案支持不完整,存在一些 bug 的情况,我们添加了全局暗黑模式开关。

这个开关支持

  1. 在特定的机型和版本上可以关闭暗黑开关

  2. 在测试用例中可以选择开启或关闭暗黑开关

3、暗黑 SDK 和组件标准化的关系

BJVZviR.png!web

暗黑 SDK 处于标准组件库的下一级,为标准组件库提供暗黑方案的支持,同时也为其他自定义组件提供支持。

四、为什么不使用苹果官方的暗黑方案?

为什么我们要独立开发一个“暗黑 SDK”,而不是直接使用苹果的官方方案呢?

苹果官方暗黑方案的几处不太便利的点
案例 苹果官方的方案 暗黑 SDK 方案 1 CGColor 适应暗黑切换 不直接支持, 需要进监听广播 支持 2 自定义属性适应暗黑切换 不直接支持, 需要进监听广播 支持 3 非 UIView 的自定义适应暗黑切换 不直接支持, 需要进监听广播 支持 4 动态颜色的使用 需要区分系统版本 直接使用 5 iOS13 以下适应暗黑切换 不支持 支持

1、CGColor 的暗黑适配

iOS 系统原生的方案:

必须监听广播,取得当前暗黑模式下的 UIColor 的值,然后根据 UIColor 提取 CGColor,代码如下:

复制代码

- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
[supertraitCollectionDidChange:previousTraitCollection];
UIColor*dyColor = [UIColorcolorWithDynamicProvider:^UIColor* _Nonnull(UITraitCollection* _Nonnull trainCollection) {
if([trainCollection userInterfaceStyle] ==UIUserInterfaceStyleLight) {
return[UIColorredColor];
}
else{
return[UIColorgreenColor];
}
}];
layer.backgroundColor = dyColor.CGColor;
}

暗黑 SDK 的方案:

无需监听,直接赋值,CGColor 也是动态颜色,可以适应暗黑变化,代码如下:

复制代码

layer.backgroundColor= [UIColor.dyColor CGColor];

2、支持自定义属性

复制代码

@interfaceMyView:UIView
@property(nonatomic,strong)UIColor* specialColor;
@end
@implementationMyView
-(void) setSpecialColor:(UIColor*)color{
// 其他代码
self.label.textColor = color;
}
#endif

在 iOS 原生的解决方案中:

复制代码

myView.specialColor= dyColor;

此动态颜色无法适应暗黑变化

如果需要完成暗黑的适配,必须监听暗黑模式变化广播,具体代码如案例 1,增加代码的复杂度和可读性。

在暗黑 SDK 中

复制代码

myView.specialColor= UIColor.dyColor;

此动态颜色可以适应暗黑变化,代码简洁。

3、支持自定义类:

复制代码

@interfaceMyObject: NSObject
@property(nonatomic,strong) UIColor* specialColor;
@end
@implementationMyObject
-(void)setSpecialColor:(UIColor*)color{
// 其他渲染代码

}
#endif

在 iOS 原生的解决方案中:

在 iOS 13 中,UIView、UIViewController,UIWindow 及其子类支持暗黑模式。其他类,比如 NSObject 则不支持暗黑。如果需要完成暗黑的适配,需要在与 MyObject 有关联的 UIView、UIViewController,UIWindow 中监听暗黑模式的变化广播,破坏了代码的模块化设计。

具体代码如案例 1,在回调函数中操作 MyObject,增加了代码的复杂度。

在暗黑 SDK 中:

复制代码

MyObject.specialColor =UIColor.dyColor;

此动态颜色可以适应暗黑变化,代码简洁。

4、iOS 系统的动态颜色只在 iOS13 以上被支持,iOS13 以下无法直接使用:

复制代码

_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property(class,nonatomic,readonly)UIColor*labelColor

在 iOS 原生的解决方案中:

需要针对当前机器的 OS 版本区别对待,或者额外提供另一个函数,封装所有的动态颜色。

复制代码

if#available(iOS 13, *) {
label.textColor = UIColor.labelColor;
} else {
label.textColor = UIColor.blackColor;
}

在暗黑 SDK 中

复制代码

label.textColor= UIColor.dyColor;

此动态颜色可以直接书写,代码简洁。

5、支持 iOS13 以下系统

在 iOS 原生的解决方案中:

不支持 iOS13 以下系统。

在暗黑 SDK 中

支持 iOS13 以下系统。

苹果官方暗黑方案的几处不太便利的点一共五点。

特别是前三点,并不是功能的缺失,只涉及到了代码改动的复杂度,需要判断 OS 版本。

而暗黑 SDK 将代码的改动简化到一行代码,这对于大规模的业务快速接入的优势是显而易见的。

另外,在业务代码加入大量的监听代码,并在监听中判断当前模式是否 UIUserInterfaceStyleLight,可维护性比较差。

五、优酷 APP 在深色模式下的效果图

YvUbYnN.png!web

Qb22U3I.png!web

emqAfqz.png!web

AruMZrB.png!web

3IVbaeU.png!web

v6bQbez.png!web

六、总结

设计标准化的逻辑为暗黑模式的快速接入奠定了基础,暗黑模式的快速实现和上线更凸显了设计标准化的重大意义。

将更多的界面元素统一到标准组件库中,实现组件集中化开发,组件的使用就能够实现跨业务,跨应用,将极大提高业务开发效率和视觉换新的成本。

作者简介:

大甘,阿里文娱无线开发专家。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK