14 | 功能组件:如何使用语义色,支持深色模式?

[toc]

前言

本文来自拉勾网课程整理

iOS 13 开始,用户可以从系统级别来把外观模式改成深色模式(Dark mode)。与原有的浅色模式(Light mode)相比,使用深色模式具有以下几大优点:

  • 由于减少发光,使用深色模式能大幅减少电量的消耗,延长 iPhone 的续航能力;
  • 对视力不佳或者与对强光敏感的用户更为友好,为他们提供更好的可视性;
  • 在暗光环境下,让用户使用手机时更舒服。

那么,我们的 App 怎样才能在支持深色模式呢?下面我将结合咱们的项目案例 Moments App 来介绍下。

iOS 语义色

对于深色模式的支持,苹果推荐使用语义化颜色(Semantic colors)来进行适配。什么叫语义化颜色呢?语义化颜色是我们根据用途来定义颜色的名称,例如使用在背景上的颜色定义为background,主文本和副文本的颜色分别定义为primaryTextsecondaryTextUI 可以通过语义色来灵活地适配用户所选择的外观模式,比如背景在浅色模式下显示为白色,而在深色模式下显示为黑色

为了简化深色模式的适配过程,苹果公司提供了具有语义的系统色(System colors)和动态系统色(Dynamic system colors)供我们使用。

971d2717460845907ab6e3f981fef03e

上图是苹果开发者网站提供的一个iOS 系统色,有蓝色、绿色、靛蓝、橙色、黄色等,它们在浅色模式和深色模式下会使用到不同的颜色值。比如蓝色,在浅色模式下,它的 RGB 分别是 0、122、255,在深色模式下则分别为10、132、255。这样就能保证系统蓝色在不同的外观模式的背景颜色上都能清晰显示

2e11c54d35517ed0457fa42e3fd08813

上图显示是 iOS 系统提供的动态系统色的定义。它们都是通过用途来定义各种颜色的名称。例如Label 用于主标签文字的颜色,而 Secondary label用于副标签文字的颜色,使用它们就能自动支持不同的外观模式了。

Moments App 的语义色

为了增强品牌效果,我们一般都会为 App 单独定义一组语义色。下面以 Moments App 为例看看如何在代码中定义语义色。

根据 07 讲的设计规范,我们在 DesignKit 组件里面自定义了一组语义色,具体代码如下:

public extension UIColor {
    static let designKit = DesignKitPalette.self
    enum DesignKitPalette {
        public static let primary: UIColor = dynamicColor(light: UIColor(hex: 0x0770e3), dark: UIColor(hex: 0x6d9feb))
        public static let background: UIColor = dynamicColor(light: .white, dark: .black)
        public static let secondaryBackground: UIColor = dynamicColor(light: UIColor(hex: 0xf1f2f8), dark: UIColor(hex: 0x1D1B20))
        public static let tertiaryBackground: UIColor = dynamicColor(light: .white, dark: UIColor(hex: 0x2C2C2E))
        public static let line: UIColor = dynamicColor(light: UIColor(hex: 0xcdcdd7), dark: UIColor(hex: 0x48484A))
        public static let primaryText: UIColor = dynamicColor(light: UIColor(hex: 0x111236), dark: .white)
        public static let secondaryText: UIColor = dynamicColor(light: UIColor(hex: 0x68697f), dark: UIColor(hex: 0x8E8E93))
        public static let tertiaryText: UIColor = dynamicColor(light: UIColor(hex: 0x8f90a0), dark: UIColor(hex: 0x8E8E93))
        public static let quaternaryText: UIColor = dynamicColor(light: UIColor(hex: 0xb2b2bf), dark: UIColor(hex: 0x8E8E93))
        static private func dynamicColor(light: UIColor, dark: UIColor) -> UIColor {
            return UIColor { $0.userInterfaceStyle == .dark ? dark : light }
        }
    }
}
public extension UIColor {
    convenience init(hex: Int) {
        let components = (
                R: CGFloat((hex >> 16) & 0xff) / 255,
                G: CGFloat((hex >> 08) & 0xff) / 255,
                B: CGFloat((hex >> 00) & 0xff) / 255
        )
        self.init(red: components.R, green: components.G, blue: components.B, alpha: 1)
    }
}


我们为UIColor定义了一个类型扩展(Extension)。为了调用时具有命名空间,我们在这个扩展里定义了一个名叫DesignKitPalette的内嵌枚举类型(Nested enum),然后定义了一个静态属性来引用该枚举。
首先,我们一起看看DesignKitPalette两个公用的方法。第一个是func dynamicColor(light: UIColor, dark: UIColor) -> UIColor,在该方法里面,我们根据用户当前选择的userInterfaceStyle来返回对应的深色或者浅色。

第二个方法是通过类型扩展来为UIColor类型添加了一个初始化函数(构造函数)。该初始化函数接收一个Int类型的参数,这个参数保存了一个十六进制的值。函数内部从hex里面取出分别表示红色、绿色和蓝色的R、GB的值,例如传入的hex0x0770e3,那么R、GB的值是分别是07、70e3, 然后把这些值传递给原有的init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)初始化函数来生成一个UIColor的实例。

有了这两个函数以后,我们就可以很方便地定义设计规范里面的各种颜色了。具体来说,只需要把浅色和深色传递给语义色的属性就可。比如,我们的语义色primary所对应的浅色和深色的十六进制分别是0x0770e30x6d9feb,那么我们就可以通过这两个值来生成一个支持动态颜色的UIColor对象,代码如下所示。

public static let primary: UIColor = dynamicColor(light: UIColor(hex: 0x0770e3), dark: UIColor(hex: 0x6d9feb))

有了这些定义以后,我们可以在代码中很方便地使用它们。代码如下:

label.textColor = UIColor.designKit.primaryText
view.backgroundColor = UIColor.designKit.background

可以看到,我们可以通过UIColor.designKit取出相应的语义色并赋值给类型为UIColor的属性即可。

测试语义色

当我们的App使用了语义色以后,要经常在浅色和深色模式之间来回切换,加以测试,及时发现问题解决问题。要不然在开发过程中可能会因为不小心引入影响可读性的Bug ,从而降低用户体验。幸运的是,iOSSimulator 为我们提供了一组快捷键Command + Shift + A来快速切换外观模式。下面是 Moments App 在不同外观模式下运行的效果。

从视频上你可以看到,当我按下快捷键Command + Shift + A的时候 Moments App 在浅色和深色模式之间自动来回切换。这样能帮我们快速检查界面上文本的可读性。

总结

在这一讲中我介绍了如何通过语义色来灵活支持不同的外观模式,同时以 Moments App 为例子介绍了如何通过UIFont的扩展来自定义语义色。

当我们的 App 使用了语义色以后,还需要注意以下几点。

  • 不要把深色模式等于黑夜模式或者夜间模式,支持深色模式的 App在正常光线的环境下也要为用户提供良好的视觉舒适度。
  • App 应该从系统设置里面读取外观模式的信息,而不是让用户在App里面进行单独配置。
  • 在开发过程中,要经常切换外观模式来测试 App
  • 要在设置 App->辅助功能->显示与字体大小页面中修改降低透明度和增强对比度开关,检查深色内容在黑色背景下的可读性。

源码地址:

定义语义色的文件地址:https://github.com/lagoueduCol/iOS-linyongjian/blob/main/Frameworks/DesignKit/src/Color/UIColorExtensions.swift

0b4b5e3b026981a72a605d427c46c340
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容