iOS13 (五)暗黑模式Dark Mode

最近公司业务需求要更换APP主题。最开始是一个地方一个地方去改,而且项目中很多老代码是用xib写的,习惯纯代码编程的我改的很难受。而且以后指不定要再次更改主题。

于是我定义了几个主要颜色的宏,代码中只要是设置颜色的地方就用宏。这样只需要改一次,当要切换主题的时候直接对宏进行更改就行了。

结合已做好的切换主题功能,再加上一个暗黑模式判断,如果当前是暗黑模式就用A套色值,如果不是就用B套色值,这样就实现了暗黑模式的适配了。


一、定义的宏:


代码中只要是设置颜色的地方就用定义好的颜色。(下面个别宏只是我的项目场景中会使用到的,并不适用于所有APP,可自行针对自己的项目定义。有些颜色两种模式下没有变动)

/// 暗黑模式 YES是

#define CKDarkMode @available(iOS 13.0, *) && UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark

// MARK: - 十六进制颜色

#define HexOf(rgbValue) Hex_A(rgbValue,1.0)

#define Hex_A(rgbValue,a) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:a]

// MARK: - 用全局变量设置背景、文字,可以优雅的主题切换 (取全局唯一性的名称,便于维护;最前面的优先级最高)

#define Color_Bg        CKDarkMode?HexOf(0x191C32):HexOf(0xf4f4f4) //背景主题颜色    黑色/白色

#define Color_ContView  CKDarkMode?HexOf(0x1D213B):HexOf(0xffffff) //内容、cell颜色  深蓝色/白色 如果背景和cell颜色一样,就都用这个

#define Color_Title    CKDarkMode?HexOf(0xFFFFFF):HexOf(0x393939) //主文字颜色      白色/黑色

#define Color_Subtitle  CKDarkMode?HexOf(0x999999):HexOf(0x999999) //副文字颜色      浅白色/灰色

#define Color_Green    CKDarkMode?HexOf(0x45C98F):HexOf(0x45C98F) //绿涨          (行情、交易)

#define Color_Red      CKDarkMode?HexOf(0xEF0C47):HexOf(0xEF0C47) //红跌          (行情、交易)

//

#define Color_NavBg    CKDarkMode?HexOf(0x1D213B):HexOf(0xffffff) //导航栏背景颜色

#define Color_NavTitle  CKDarkMode?HexOf(0xFFFFFF):HexOf(0x393939) //导航栏标题颜色

#define Color_TabbarBg  CKDarkMode?HexOf(0x1D213B):HexOf(0xffffff) //标签栏背景颜色

#define Color_Selected  CKDarkMode?HexOf(0x46CA8F):HexOf(0x46CA8F) //绿色 (按钮选中、已认证状态的颜色)

#define Color_Line      CKDarkMode?HexOf(0x191C32):HexOf(0xf4f4f4) //分割线

#define Color_DarkGray  CKDarkMode?HexOf(0x333333):HexOf(0x333333) //深灰色

#define Color_Gray      CKDarkMode?HexOf(0x666666):HexOf(0x666666) //灰色

#define Color_LightGray CKDarkMode?HexOf(0x999999):HexOf(0x999999) //浅灰色

#define Color_InputBg  CKDarkMode?HexOf(0x191C32):HexOf(0xf4f4f4) //输入框背景颜色

#define Color_DarkBlue  CKDarkMode?HexOf(0x191C32):HexOf(0x191C32) //深蓝色 (特殊颜色)

#define Color_HalfTitle CKDarkMode?Hex_A(0x999999, 0.5):Hex_A(0x999999, 0.5);//半透明文字 色值是副标题的一半

如果想关闭暗黑模式,直接设置:

#define CKDarkMode NO


二、遇到的问题:


1、最开始我是用的self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark去做判断,但是有些类并没有UITraitCollection这个属性,很多地方报错。

解决方案:

改用UITraitCollection.current属性来获取当前App的颜色模式。

2、CGColorRef相关:

bt.layer.borderColor = Color_Selected.CGColor;

报错:

Incompatible operand types ('UIColor * _Nonnull' and 'CGColorRef _Nonnull' (aka 'struct CGColor *'))

解决方案:

UIColor *color = Color_Selected;

bt.layer.borderColor = color.CGColor;

3.每次打开APP都能展示正常的模式;但是如果打开APP后再切换模式,已经加载出来的页面依然会显示切换之前的主题模式。

解决方案:

在页面中添加通知,获取到切换主题的通知后重新刷新一下页面颜色(类似于项目中的国际化通知处理逻辑)

4.项目中个别页面的状态栏是固定的白色,在切换页面的时候会把状态栏切换回主题颜色黑色,但是在暗黑模式下就会有问题,因为暗黑模式下整个APP的状态栏都是白色的,这时不需要切换回黑色。

解决方案:

添加一个UIStatusBarStyle变量记录主题状态栏颜色,这样可以不用在控制器内做太多额外的判断。如果用 @available(iOS 13.0, *) 去做判断,需求变更后还要每个地方都去改动代码。用了这种方式,后面即使更改了主题或者关闭了暗黑模式,也不用一一去改代码;也可以通过上面定义的宏CKDarkMode做判断,关闭暗黑模式时只需把CKDarkMode设置为NO就行

UIStatusBarStyle _themeStatusBarStyle;//记录主题状态栏颜色

- (void)viewWillAppear:(BOOL)animated {

    [super viewWillAppear:animated];

    _themeStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;

    // 设置状态栏颜色为白色

    [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;

}

- (void)viewWillDisappear:(BOOL)animated{

    [super viewWillDisappear:animated];

    // 恢复状态栏颜色为主题颜色

    [UIApplication sharedApplication].statusBarStyle = _themeStatusBarStyle;

}

5、使用了宏的地方都会报警告,提示我要做系统版本判断,但是实际上我已经在CKDarkMode中判断过了,系统检测不到:

'currentTraitCollection' is only available on iOS 13.0 or newer

解决方案:使用UIColor扩展。

999+的警告有点影响代码视觉体验,后面应该会改用扩展的方式。如果有更好的解决方案请在下方留言。


三、UITraitCollection介绍:


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

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

2、另外,我们还可以通过 UITraitCollection.current这个属性来获取当前 App 的颜色模式。

3、如果暂时不想开放这个功能,可以先暂时全局关闭暗黑模式:

在 Info.plist 文件中,添加 key 为 User Interface Style,类型为 String,value 设置为 Light (Dark)即可,如果重新打开就把这条设置删除。(这种方式是在APP整个生命周期内关闭了暗黑模式;上面的设置#define CKDarkMode NO只是用代码做了判断并只在用了宏的地方起作用)。

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

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

self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;

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

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


四、拓展


除了我的这种实现方案,还有其他方案可以适配暗黑模式:

1、UIColor扩展:

+(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;

}}

问题:

这种写法要在每个使用的地方分别传一个普通模式的颜色和暗黑模式的颜色,不方便维护。

解决方案:

可以定义几个常用颜色函数,特殊的场景就用上面的方法,这样就不需要在每个地方都控制颜色值了。

+(UIColor *)ContViewColor{

    if(@available(iOS 13.0, *)) {

        UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {

            if(traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {

                return ColorA;           

            }else{

                return ColorB;           

       } }];

        return dyColor;   

    }else{

        return ColorB;   

}}

2、可以在 Images.xcassets中定义几种常用颜色,并为颜色再配置一个用于暗黑模式的对应的颜色:

3、在 Images.xcassets中配置不同模式下的图片,当你设置为暗黑模式后就会自动显示对应的图片

4、启动图:

LaunchScreen.storyboard可以像普通的图片那样针对深色模式设置另外的一张图片

5、layer:

UIColor *resolvedColor = [[UIColor labelColor] resolvedColorWithTraitCollection:self.view.traitCollection];

label.layer.borderColor = resolvedColor.CGColor;

6、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",

};

7、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,foruse on dark backgrounds   

    UIStatusBarStyleDarkContent  = 3, // Dark content,foruse on light backgrounds

};

8、SF Symbols


注意:

命名时要保证这个名字的全局唯一性,避免和项目中其他命名雷同,这样可以保证全局搜索时搜索到的结果只有你想搜索的内容,便于维护。例如你取名RedColor,会搜索到很多其他没用的信息。这种命名思路也可以用在其他地方。

除了改背景颜色、文字颜色,还需要替换图标、图片,这个需要UI配合切图。

参考:

How To Adopt Dark Mode In Your iOS App

DarkMode1

DarkMode2

DarkMode3

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

推荐阅读更多精彩内容