注意:
    同一工程内多个Assets文件在打包后,就会生成一个Assets.car 文件,所以要保证Assets内资源文件的名字不能相同。
    苹果官方强烈建议适配暗黑模式(Dark Mode)此功能也是为了开发者能慢慢将应用适配暗黑模式,所以想通过此功能不进行适配暗黑模式,预计将会被拒。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:812157648,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
暗黑模式的优点:
- 省电
 - 沉浸式效果明显 深色背景的优势是可以突出与我们主要交互操作的内容,弱化其他辅助元素并降低屏幕整体的亮度减少视觉压力。
 
全局关闭暗黑模式
在Info.plist 文件中,添加UIUserInterfaceStyle key 名字为 User Interface Style 值为String, 将UIUserInterfaceStyle key 的值设置为 Light
单个界面不遵循暗黑模式
UIViewController与UIView 都新增一个属性 overrideUserInterfaceStyle
将 overrideUserInterfaceStyle 设置为对应的模式,则强制限制该元素与其子元素以设置的模式进行展示,不跟随系统模式改变进行改变
- 设置 ViewController 的该属性, 将会影响视图控制器的视图和子视图控制器采用该样式
 - 设置 View 的该属性, 将会影响视图及其所有子视图采用该样式
 - 设置 Window 的该属性, 将会影响窗口中的所有内容都采用样式,包括根视图控制器和在该窗口中显示内容的所有演示控制器(UIPresentationController)
 
如何在代码里进行适配颜色(UIColor)
+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
- (UIColor *)initWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
e.g.
[UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trait) {
    if (trait.userInterfaceStyle == UIUserInterfaceStyleDark) {
        return UIColorRGB(0x000000);
    } else {
        return UIColorRGB(0xFFFFFF);
    }
 }];
系统调用更新方法,自定义重绘视图
当用户更改外观时,系统会通知所有window与View需要更新样式,在此过程中iOS会触发以下方法
UIView
traitCollectionDidChange(_:)
layoutSubviews()
draw(_:)
updateConstraints()
tintColorDidChange()
UIViewController
traitCollectionDidChange(_:)
updateViewConstraints()
viewWillLayoutSubviews()
viewDidLayoutSubviews()
UIPresentationController
traitCollectionDidChange(_:)
containerViewWillLayoutSubviews()
containerViewDidLayoutSubviews()
适配Dark Mode
- 颜色适配
 - 图片适配
 
1 颜色适配
- iOS13系统动态颜色
 
iOS13 之前 UIColor只能表示一种颜色,而从 iOS13 开始UIColor是一个动态的颜色,在LightMode和Dark Mode可以分别设置不同的颜色。 iOS13系统提供了一些动态颜色
@property (class, nonatomic, readonly) UIColor *systemBrownColor        API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemIndigoColor       API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemGray2Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray3Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray4Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray5Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray6Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *labelColor              API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *secondaryLabelColor     API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *tertiaryLabelColor      API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *quaternaryLabelColor    API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *linkColor               API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *placeholderTextColor    API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *separatorColor          API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *opaqueSeparatorColor    API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemBackgroundColor                   API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemBackgroundColor          API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemBackgroundColor           API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGroupedBackgroundColor            API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemGroupedBackgroundColor   API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemGroupedBackgroundColor    API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemFillColor                         API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemFillColor                API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemFillColor                 API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *quaternarySystemFillColor               API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
① 实例
[self.view setBackgroundColor:[UIColor systemBackgroundColor]];
[self.titleLabel setTextColor:[UIColor labelColor]];
[self.detailLabel setTextColor:[UIColor placeholderTextColor]];
效果展示
用法和iOS13之前的一样,使用系统提供的这些动态颜色,不需要其他的适配操作
自定义动态UIColor 在实际开发过程,系统提供的这些颜色还远远不够,因此我们需要创建更多的动态颜色
初始化动态UIColor方法
iOS13 UIColor增加了两个初始化方法,使用以下方法可以创建动态UIColor 注:一个是类方法,一个是实例方法
+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
- (UIColor *)initWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
- 这两个方法要求传一个block进去
 - 当系统在LightMode和DarkMode之间相互切换时就会触发此回调
 - 这个block会返回一个UITraitCollection类
 - 我们需要使用其属性userInterfaceStyle,它是一个枚举类型,会告诉我们当前是LightMode还是DarkMode
 
typedef NS_ENUM(NSInteger, UIUserInterfaceStyle) {
    UIUserInterfaceStyleUnspecified,
    UIUserInterfaceStyleLight,
    UIUserInterfaceStyleDark,
} API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos);
② 实例
UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
        if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
            return [UIColor redColor];
        }
        else {
            return [UIColor greenColor];
        }
    }];
    
 [self.bgView setBackgroundColor:dyColor];
效果展示
2 图片适配
- 
打开Assets.xcassets
 - 
新建一个Image set
 - 
打开右侧工具栏,点击最后一栏,找到Appearances,选择Any,Dark
 - 
将两种模式下不同的图片资源都拖进去
 - 
使用该图片
 
[_logoImage setImage:[UIImage imageNamed:@"icon_logo"]];
大功告成,完成了颜色和图片的Dark Mode适配。
获取当前模式(Light or Dark)
有时候我们需要知道当前处于什么模式,并根据不同的模式执行不同的操作 iOS13中CGColor依然只能表示单一的颜色通过调用UITraitCollection.currentTraitCollection.userInterfaceStyle获取当前模式
if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
        [self.titleLabel setText:@"DarkMode"];
    }
    else {
        [self.titleLabel setText:@"LightMode"];
    }
    
3. 其他
1.监听模式切换
有时我们需要监听系统模式的变化,并作出响应 那么我们就需要在需要监听的viewController中,重写下列函数
// 注意:参数为变化前的traitCollection
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;
// 判断两个UITraitCollection对象是否不同
- (BOOL)hasDifferentColorAppearanceComparedToTraitCollection:(UITraitCollection *)traitCollection;
① 示例
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
    [super traitCollectionDidChange:previousTraitCollection];
    // trait发生了改变
    if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
    // 执行操作
    }
    }
    
2.CGColor适配
我们知道iOS13后,UIColor能够表示动态颜色,但是CGColor依然只能表示一种颜色,那么对于CALayer等对象如何适配暗黑模式呢?当然是利用上一节提到的监听模式切换的方法啦。
① 方式一:resolvedColor
// 通过当前traitCollection得到对应UIColor
// 将UIColor转换为CGColor
- (UIColor *)resolvedColorWithTraitCollection:(UITraitCollection *)traitCollection;
实例
- (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];
        }
    }];
    UIColor *resolvedColor = [dyColor resolvedColorWithTraitCollection:previousTraitCollection];
    layer.backgroundColor = resolvedColor.CGColor;
    
② 方式二:performAsCurrent
// 使用当前trainCollection调用此方法
- (void)performAsCurrentTraitCollection:(void (^)(void))actions;
示例
- (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];
        }
    }];
    [self.traitCollection performAsCurrentTraitCollection:^{
        layer.backgroundColor = dyColor.CGColor;
    }];
    
}
方式三:最简单的方法 直接设置为一个动态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;
}
4.强行设置App模式
当系统设置为Light Mode时,对某些App的个别页面希望一直显示Dark Mode下的样式,这个时候就需要强行设置当前ViewController的模式了
// 设置当前view或viewCongtroller的模式
@property(nonatomic) UIUserInterfaceStyle overrideUserInterfaceStyle;
示例
// 设置为Dark Mode即可
[self setOverrideUserInterfaceStyle:UIUserInterfaceStyleDark];
⚠️ 注意!!!
- 当我们强行设置当前viewController为Dark Mode后,这个viewController下的view都是Dark Mode
 - 由这个ViewController present出的ViewController不会受到影响,依然跟随系统的模式
 - 要想一键设置App下所有的ViewController都是Dark Mode,请直接在Window上执行
overrideUserInterfaceStyle - 对
window.rootViewController强行设置Dark Mode也不会影响后续present出的ViewController的模式 
5.NSAttributedString优化
对于UILabel、UITextField、UITextView,在设置NSAttributedString时也要考虑适配Dark Mode,否则在切换模式时会与背景色融合,造成不好的体验
推荐的做法
// 添加一个NSForegroundColorAttributeName属性
NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:[UIColor labelColor]};
NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
总结
总的来说,iOS13主要有以下变化: 1.支持 Dark Mode 2.UIColor变为动态颜色 3.更新StatusBar样式 4.更新UIActivityIndicatorView样式
参考文档
原文作者:沐雨07