iOS13 (五)暗黑模式Dark Mode

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

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

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

.bash

1、定义的宏:

代码中只要是设置颜色的地方就用定义好的颜色。(下面个别宏只是个人项目场景中会使用到的,并不适用于全部APP,可自行针对本身的项目定义。有些颜色两种模式下没有变更)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);//半透明文字 色值是副标题的一半
复制代码

若是想关闭暗黑模式,直接设置:ide

#define CKDarkMode NO函数

.post

2、遇到的问题:

一、最开始我是用的self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark去作判断,可是有些类并无UITraitCollection这个属性,不少地方报错。

解决方案:ui

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

二、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;
}
复制代码

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

'currentTraitCollection' is only available on iOS 13.0 or newer

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

.

3、UITraitCollection介绍:

一、在 iOS 13 中,咱们能够经过 UITraitCollection 来判断当前系统的模式。UIView 和 UIViewController 、UIScreen、UIWindow 都已经听从了UITraitEnvironment这个协议,所以这些类都拥有一个叫作 traitCollection的属性,在这些类中,咱们能够这样去判断当前 App 的颜色模式:

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

二、另外,咱们还能够经过 UITraitCollection.current这个属性来获取当前 App 的颜色模式。

三、若是暂时不想开放这个功能,能够先暂时全局关闭暗黑模式:

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

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

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

self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;

设置此属性会影响当前view / viewController / window 以及它下面的任何内容。
若是你但愿一个子视图监听系统的模式,请将 overrideUserInterfaceStyle 属性设置为.unspecified

.

4、拓展

除了个人这种实现方案,还有其余方案能够适配暗黑模式:

一、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;
    }
}
复制代码

二、能够在 Images.xcassets 中定义几种经常使用颜色,并为颜色再配置一个用于暗黑模式的对应的颜色:

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

四、启动图:

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

五、layer:

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

六、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
};
复制代码

八、SF Symbols

.

注意:

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

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

参考:

How To Adopt Dark Mode In Your iOS App
DarkMode1
DarkMode2
DarkMode3

相关文章
相关标签/搜索