美文网首页iOS Developerios 13iOS-移动架构师
iOS13 暗黑模式(Dark Mode)适配之OC版

iOS13 暗黑模式(Dark Mode)适配之OC版

作者: 勤奋的小卫子 | 来源:发表于2019-06-13 23:57 被阅读85次

    目录

    • 一、适配Dark Mode
      • 颜色适配
      • 图片适配
    • 二、获取当前模式(Light or Dark)
    • 三、其他内容
    • 四、总结

    首先看看我们的效果图:

    适配效果图

    一、适配Dark Mode

    开发者主要从颜色和图片两个方面进行适配,我们不需要关心切换模式时该如何操作,这些都由系统帮我们实现

    1 颜色适配

    • iOS13 之前 UIColor只能表示一种颜色,而从 iOS13 开始UIColor是一个动态的颜色,在Light ModeDark 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]];
    
    

    ② 效果展示

    系统UIColor样式

    用法和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进去
    • 当系统在LightModeDarkMode之间相互切换时就会触发此回调
    • 这个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];
    
    

    效果展示

    自定义UIColor效果

    接下来我们看看如何适配图片

    2.图片适配

    • 打开Assets.xcassets
    • 新建一个Image set
    默认显示效果
    • 打开右侧工具栏,点击最后一栏,找到Appearances,选择Any,Dark
      侧边栏
    • 将两种模式下不同的图片资源都拖进去
    两种不同模式
    • 使用该图片
    [_logoImage setImage:[UIImage imageNamed:@"icon_logo"]];
    
    最终效果图

    大功告成,完成颜色和图片的Dark Mode适配,是不是很easy呢

    ① 获取当前模式(Light or Dark)

    有时候我们需要知道当前处于什么模式,并根据不同的模式执行不同的操作
    iOS13中CGColor依然只能表示单一的颜色
    通过调用UITraitCollection.currentTraitCollection.userInterfaceStyle
    获取当前模式

    实例

    if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
            [self.titleLabel setText:@"DarkMode"];
        }
        else {
            [self.titleLabel setText:@"LightMode"];
        }
    
    

    三、其他

    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;
    }
    

    ⚠️!!! 设置layer颜色都是在traitCollectionDidChange中,意味着如果没有发生模式切换,layer将会没有颜色,需要设置一个基本颜色

    3.模式切换时打印log

    模式切换时自动打印log,就不需要我们一次又一次的执行po命令了

    • 在Xcode菜单栏Product->Scheme->Edit Scheme
    • 选择Run->Arguments->Arguments Passed On Launch
    • 添加以下命令即可
      • -UITraitCollectionChangeLoggingEnabled YES
        模式切换打印log

    4.强行设置App模式

    当系统设置为Light Mode时,对某些App的个别页面希望一直显示Dark Mode下的样式,这个时候就需要强行设置当前ViewController的模式了

    // 设置当前view或viewCongtroller的模式
    @property(nonatomic) UIUserInterfaceStyle overrideUserInterfaceStyle;
    

    示例

    // 设置为Dark Mode即可
    [self setOverrideUserInterfaceStyle:UIUserInterfaceStyleDark];
    

    ⚠️ 注意!!!

    • 当我们强行设置当前viewControllerDark 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,否则在切换模式时会与背景色融合,造成不好的体验

    不建议的做法

    NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16]};
    NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
    

    推荐的做法

    // 添加一个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样式

    完整iOS13新特性请参考以下文章:
    iOS13 新特性简介
    iOS13-适配夜间模式/深色外观(Dark Mode)
    )

    相关文章

      网友评论

        本文标题:iOS13 暗黑模式(Dark Mode)适配之OC版

        本文链接:https://www.haomeiwen.com/subject/ksryfctx.html