NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

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

  • 2020-02-26
  • 本文字数:4303 字

    阅读完需:约 14 分钟

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

一、概述

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 层次结构中的位置。



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


  • 暗黑模式开关的设置

  • 对各类 View 的相关方法进行注册

  • 装载预置的主题集,比如深色和浅色两种主题

  • 自定义动态颜色的初始化。


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


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


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

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

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

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

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

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


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


为什么采用遍历的方式?


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


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


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


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


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

二、动态颜色的支持


从最简单的案例开始



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



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


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



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


2、解决方案:


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


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


涉及到图片的案例

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

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


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


解决方案


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



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

2、Token 的设计


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


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


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

3、全局暗黑模式开关

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


这个开关支持


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

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

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


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

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

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


苹果官方暗黑方案的几处不太便利的点


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

1、CGColor 的暗黑适配

iOS 系统原生的方案


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


- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {    [super traitCollectionDidChange:previousTraitCollection];    UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {        if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {            return [UIColor redColor];        }        else {            return [UIColor greenColor];        }    }];        layer.backgroundColor = dyColor.CGColor;}
复制代码


暗黑 SDK 的方案


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


layer.backgroundColor = [UIColor.dyColor CGColor];
复制代码

2、支持自定义属性

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


在 iOS 原生的解决方案中


myView.specialColor = dyColor;
复制代码


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


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


在暗黑 SDK 中


myView.specialColor = UIColor.dyColor;
复制代码


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

3、支持自定义类:

@interface MyObject : NSObject@property (nonatomic,strong) UIColor* specialColor;@end@implementation MyObject-(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 在深色模式下的效果图






六、总结

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


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


作者简介


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


相关阅读


优酷暗黑模式(一):是什么、为什么、如何落地?


优酷暗黑模式(二):如何建立设计语言标准化管理体系


优酷暗黑模式(三):暗黑模式设计指南


优酷暗黑模式(四):设计标准化的技术实现


优酷暗黑模式(五):暗黑模式的技术实现策略


2020-02-26 15:002146

评论

发布
暂无评论
发现更多内容

收藏+下载!Flink 社区最全学习渠道汇总

Apache Flink

flink

监控应用,应该监控什么?

小清新同学

云计算 运维 监控

从大数据的角度来谈谈运维监控这件事儿

小清新同学

运维 监控

c++基础——杂谈2

菜鸟小sailor 🐕

c++ 语言

Python 自动化测试全攻略:五种自动化测试模型实战详解

葡萄城技术团队

自动化测试

娱乐圈套路多?看区块链如何来破解

CECBC

网红 娱乐圈

保留时序数据波动细节的一种采样算法

小清新同学

监控 时序数据库

传销资金盘挂靠区块链热点 肃清整顿热潮拉开帷幕

CECBC

区块链 金融

数据库-技术专题-SQL编写规范

洛神灬殇

架构师训练营第 1 期第 2 周学习总结

owl

极客大学架构师训练营

缓存解决方案-技术专题-Caffeine Cache

洛神灬殇

难得干货,揭秘支付宝的2维码扫码技术优化实践之路

JackJiang

支付宝

架构师训练营第 1 期第二周课后练习题

Leo乐

极客大学架构师训练营

刷爆朋友圈的字节跳动编码题,今天把解析思路分享下!

Java架构师迁哥

虚拟卡兑换架构设计

孙志平

高难度对话读书笔记—认知篇2

wo是一棵草

关于Java 编译Servlet或者自定义Tag,引入包的问题

谷鱼

Java

架构师训练营第二周学习总结

尹斌

华为轮值董事长郭平2020全联接大会主题演讲:永远面向阳光,阴影甩在身后

华为云开发者联盟

5G ICT huawei

项目实战,动态增删form表单

麦洛

jquery 克隆

架构师训练营第二周作业

尹斌

什么才是“应用拓扑”?

小清新同学

运维 监控

RN运行项目报错:Unable to resolve module `./debugger-ui/debuggerWorker.js` from ``

凌宇之蓝

ios android React Native

跟着B站UP主小姐姐去华为坂田基地采访扫地僧

华为云开发者联盟

华为 技术 大牛 扫地僧

如何设计Go语言中的channel

soolaugust

channel goroutines Go 语言

架构师训练营第 2 周作业

netspecial

极客大学架构师训练营

MySQL varchar类型最大值,原来一直都理解错了

架构精进之路

MySQL varchar

2B还是2C,这真是个问题

MavenTalker

SaaS

Go中的HTTP请求之——HTTP1.1请求流程分析

Gopher指北

HTTP Go web Go 语言

Dolphinscheduler系统架构设计

dll

Apache DolphinScheduler

架构师训练营第 1 期第 2周作业

owl

极客大学架构师训练营

优酷暗黑模式(六):暗黑模式的技术支撑 iOS_移动_阿里巴巴文娱技术_InfoQ精选文章