iOS 13 DarkMode 暗黑模式

iOS 13终于引来了暗黑模式。

每当新特性的到来,iOS开发者们既紧张又有点小兴奋,怀揣着被虐的心态,让我们来看看暗黑模式到底是个啥。

虽然这是一个设计驱动的需求,但是本着一颗虔诚(暴躁)的心,我们还是要一步步来,“工欲善其事、必先利其器”,搞清楚应该怎么做。

下载 Xcode11,跑起来你的项目。模拟器启动后,在“设置”-“开发者”中打开“Dark Appearance”后,你的项目就自动运行在暗黑模式了。

有些界面会很奇怪,这很正常,可以从一个界面入手,一步步优化它。

UITraitCollection

先来了解下 UITraitCollection。

在 iOS 13 中,我们可以通过 UITraitCollection 来判断当前系统的模式。UIView 和 UIViewController 、UIScreen、UIWindow 都已经遵从了UITraitEnvironment 这个协议,因此这些类都拥有一个叫做 traitCollection 的属性,在这些类中,我们可以这样去判断当前 App 的颜色模式:

BOOL isDark = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark);

另外,我们还可以通过 UITraitCollection.current 这个属性来获取当前 App 的颜色模式。但是并不是所有的时候获取的都是正确的,只有在下面这些方法中,才可以放心的使用这个属性:

UIView UIViewController UIPresentationController
draw()
layoutSubview() viewWillLayoutSubviews() viewDidLayoutSubviews() containerViewWillLayoutSubviews() containerViewDidLayoutSubviews()
traitCollectionDidChange() tintColorDidChange() traitCollectionDidChange() traitCollectionDidChange()

还没适配完又不想给用户看,可以先暂时全局关闭暗黑模式:
在 Info.plist 文件中,添加 key 为 User Interface Style,类型为 String,value 设置为 Light 即可。

infoplist.png

在 iOS 13中,UIView、UIViewController 、UIWindow 有了一个 overrideUserInterfaceStyle 的新属性,可以覆盖系统的模式。

单个页面或视图关闭暗黑模式,设置 overrideUserInterfaceStyle 为对应的模式,强制限制该视图与其子视图以设置的模式进行展示,不跟随系统模式改变进行改变。

self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;

设置此属性会影响当前view/viewController/window 以及它下面的任何内容。

如果你希望一个子视图监听系统的模式,请将 overrideUserInterfaceStyle 属性设置为.unspecified。

颜色

系统颜色

在 iOS 13中,苹果引入了全新系统颜色,系统颜色是动态的,会根据当前系统是默认模式还是暗黑模式动态调整颜色。

苹果还提供了一组动态的灰度颜色。

systemcolor.png

语义化颜色

苹果提供了一套根据颜色使用目的而约定颜色命名的语义化颜色。语义化颜色也是动态颜色。

例如系统背景色(SystemBackground)、分割线颜色(SeparatorColor)等等,使用语义化颜色确保你的 App 具有和系统应用高度类似的配色风格。通过使用语义化颜色,可以让你的 App 更加贴近原生体验,这是官方希望达到的最佳体验。

elementcolor.png

以上两种动态颜色,是 iOS 13 才支持的新特性,并且颜色有限,大概率不能满足 App 的使用,继续往下看。

自定义颜色:通过 Assets 配置

在 iOS 11和 Xcode 9 中,我们可以在 Images.xcassets 中添加颜色。

现在我们可以为颜色再配置一个用于暗黑模式的对应的颜色。

assets.png

通过代码自定义颜色

+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider;
- (UIColor *)initWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider;

iOS 13 提供了的新方法,可以在 block 中判断 traitCollection.userInterfaceStyle,根据系统模式设置返回的颜色。

+(UIColor *)generateDynamicColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor{
    if (@available(iOS 13.0, *)) {
        UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {
                return lightColor;
            }else {
                return darkColor;
            }
        }];
        return dyColor;
    }else{
        return lightColor;
    }
}


图片

SF Symbols

苹果在 WWDC19 上引入了 SF Symbols 。SF Symbols是一个巨大的矢量图标库(超过1500),可供开发人员在自己的 App 中使用。

更多可以查看关于 SF Symbols 的 Session 206-Introducing SF Symbols,下载.dmg可以查看全部图标。苹果在自己的应用中使用,也推荐开发者使用。

if (@available(iOS 13.0, *)) {
        _iconIV.image = [UIImage systemImageNamed:@"plus.circle"];
}

不想用 SF Symbols 的话,还有一种方式就是和自定义颜色相同的方法,在 Images.xcassets 中配置不同模式下的图片。

assets_image.png

在 Images.xcassets 中设置颜色和图片是向下兼容的,iOS 13 以下的系统会默认取Any状态下的设定,iOS 13 会根据系统模式取AnyDark下的设定。

模糊效果

在 iOS 中,我们创建模糊效果的 UIVisualEffectView 的代码也会有明确表面明暗的情况。

在 iOS 13 中,我们需要让这些模糊效果随着系统模式的切换而切换。UIKit 提供了新的模糊样式且是动态的,会随着系统模式的改版而自动匹配。

利用 UIVisualEffectView 来创建一些类似模糊的效果时,不要设置类似 UIBlurEffectStyleExtraLight 这样带有明确颜色的效果,而是设置 UIKit 中新提供的动态样式的效果,比如 UIBlurEffectStyleSystemThinMaterialLight 或 UIBlurEffectStyleSystemThinMaterialDark。

UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemMaterial];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];

启动图

如果我们的 App 使用了 LaunchImage 作为启动屏,那么就应该考虑把 LaunchImage 换成 LaunchScreen.storyboard 了,因为在 Xcode 11 中 LaunchImage 并不能像普通的图片那样针对深色模式设置另外的一张图片。

富文本

给 Attributed String 加上一个 foregroundColor 属性,设置动态的颜色即可。

[attr addAttribute:NSForegroundColorAttributeName value:[UIColor labelColor] range:NSMakeRange(10, 20)];

layer

关于 layer 的话,可以通过调用 view. traitcollection 从 view 中获得当前的 UITraitCollection。

UIColor *resolvedColor = [[UIColor labelColor] resolvedColorWithTraitCollection:self.view.traitCollection];
label.layer.borderColor = resolvedColor.CGColor;

如果我们需要解析多种颜色,UITraitCollection 有一个方便的 performAsCurrent() 方法:

[self.view.traitCollection performAsCurrentTraitCollection:^{
    for (UIColor *color in self.colorsArr) {
        // 批量拿到 color.CGColor
    }
}];

调试 dark mode

storyboards 和 xib 文件,现在可以切换界面之间的样式是默认还是暗黑了。

在模拟器上,也可以直接切换暗黑模式进行调试了。

debug.JPG

一些新变更

UIActivityIndicatorView 的 style 变更了。
iOS 13前 的 UIActivityIndicatorViewStyle:

typedef NS_ENUM(NSInteger, UIActivityIndicatorViewStyle) {
    UIActivityIndicatorViewStyleWhiteLarge,
    UIActivityIndicatorViewStyleWhite,
    UIActivityIndicatorViewStyleGray,
};

iOS 13后,由于暗黑模式,上述三个属性都被废弃,建议使用如下 style:

typedef NS_ENUM(NSInteger, UIActivityIndicatorViewStyle) {
    UIActivityIndicatorViewStyleMedium,
    UIActivityIndicatorViewStyleLarge,
    UIActivityIndicatorViewStyleWhiteLarge API_DEPRECATED_WITH_REPLACEMENT("UIActivityIndicatorViewStyleLarge", 
    UIActivityIndicatorViewStyleWhite API_DEPRECATED_WITH_REPLACEMENT("UIActivityIndicatorViewStyleMedium",
    UIActivityIndicatorViewStyleGray API_DEPRECATED_WITH_REPLACEMENT("UIActivityIndicatorViewStyleMedium", 
};

Status Bar 的 style 变更了。

在 iOS 13 之前,状态栏的样式的枚举值也带有着明显的颜色倾向,UIStatusBarStyleDefault、UIStatusBarStyleLightContent。

现在,状态栏的 default 样式会根据当前的模式展示不同的颜色,而原有的 lightContent 样式则新增一个 darkContent 的样式与之对应。

typedef NS_ENUM(NSInteger, UIStatusBarStyle) {
    UIStatusBarStyleDefault      = 0, // Automatically chooses light or dark content based on the user interface style
    UIStatusBarStyleLightContent = 1, // Light content, for use on dark backgrounds
    UIStatusBarStyleDarkContent  = 3, // Dark content, for use on light backgrounds
};


参考资料:

How To Adopt Dark Mode In Your iOS App

Backwards compatibility for iOS 13 system colors

写给设计师的指南:iOS 13 Dark Mode 深度解析

作者简介

就职于甜橙金融(翼支付)信息技术部,负责 iOS 客户端开发。

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

推荐阅读更多精彩内容