美文网首页
关于iOS13 DarkMode的适配那些事儿

关于iOS13 DarkMode的适配那些事儿

作者: 重庆妹子在霾都 | 来源:发表于2020-03-15 16:01 被阅读0次

    一、为什么要调研这个暗黑模式的适配?

    在2020年3月4日,苹果粑粑可能心情大好发了这么一个公告:


    image.png

    在这个公告里面,苹果提了三个要求:
    1.至2020年4月30日起,开发者必须使用iOS1或更高版本打包才能提交到App Store
    2.至2020年4月30日起,开发者必使用storyboard来提供应用的launchScreen
    3.至2020年4月30日起,开发者需要给自己的应用完成所有屏幕尺寸的适配

    公告中提到了Dark Mode,但是并没有明确表示需要在2020年4月30日之前完成Dark Mode的适配,所以关于网上那些说不适配Dark Mode会导致应用下架的完全是无稽之谈,那么可能很多童鞋又会问Apple是不是支持开发者做这个Dark Mode,答案是肯定的,苹果为了Dark Mode做了很多工作,比如说提供了一些接口、各种适配指南等,但是这种支持只是“强烈建议”还远远没有上升到不支持Dark Mode就要下架的程度。
    个人认为,在遥远的未来,可能苹果粑粑真的会要求开发者要对自己的 应用做Dark Mode的适配,话不多说,我们接下来就来看看怎么来做这个暗黑模式(Dark Mode)的适配?

    二、Dark Mode有哪些适配方案,各自的优缺点是什么?

    对于广大iOS开发者来说,适配Dark Mode并不像设置语言那么简单,设置语言之后,手机会重启,即便是开发者自己实现的语言切换的功能 切换语言也会让App重新初始化,相反Dark Mode是要求在切换主题之后,App在运行状态中去更新配色和素材,这也是适配暗黑模式的难点所在,而且对于开发者来说,工作量无疑是巨大的。

    苹果提供的适配方案主要有两个:

    1、将两种主题不同的素材直接存储在对象中,UIKit在主题变化时获取对应的素材更新展示。

    优点:工作量相对较少,对开发者比较友好
    缺点:灵活性差

    2、给出主题变化的通知,让开发者在主题变化的通知回调里面做相应的适配工作。

    优点:高度自定义,灵活性非常强
    缺点:适配工作工作量巨大
    对于不同的适配方案,开发者需要根据应用的实际情况去选择相应的适配方案,下面分别针对两种方案做一下实际适配:

    1.1 颜色的适配
    1.1.1 使用UIKIt提供的动态颜色
    - (UILabel *)textLabel {
        if (!_textLabel) {
            _textLabel = [[UILabel alloc] initWithFrame:CGRectMake(37, 50, kLabelWidth, kLabelHeight)];
            _textLabel.backgroundColor = [UIColor lightGrayColor];
            _textLabel.layer.cornerRadius = 30.0;
            _textLabel.clipsToBounds = YES;
            _textLabel.text = @"使用UIKit提供的动态颜色";
            _textLabel.textAlignment = NSTextAlignmentCenter;
            if (@available(iOS 13.0, *)) {
                _textLabel.textColor  = UIColor.secondaryLabelColor;
            }else {
                _textLabel.textColor = [UIColor redColor];
            }
            _textLabel.font = [UIFont systemFontOfSize:16.0 weight:UIFontWeightMedium];
        }
        return _textLabel;
    }
    

    secondaryLabelColor、labelColor等这些颜色都内置了对两套主题的适配,在iOS13下,UIKit 提供的视图组件的背景颜色、文字颜色等属性都是适配过两种主题的颜色,创建的视图组件即使没有手动设置颜色,也是已经适配过两种主题的。

    1.1.2 创建动态颜色
    - (UILabel *)textLabel1 {
        if (!_textLabel1) {
            _textLabel1 = [[UILabel alloc] initWithFrame:CGRectMake(37, 50 + kLabelHeight + 20, kLabelWidth, kLabelHeight)];
            _textLabel1.backgroundColor = [UIColor lightGrayColor];
            _textLabel1.layer.cornerRadius = 30.0;
            _textLabel1.clipsToBounds = YES;
            _textLabel1.text = @"动态的创建颜色";
            _textLabel1.textAlignment = NSTextAlignmentCenter;
            if (@available(iOS 13.0, *)) {
                _textLabel1.textColor  = [UIColor ww_colorWithLightColor:[UIColor redColor] darkColor:[UIColor greenColor]];
            }else {
                _textLabel1.textColor = [UIColor redColor];
            }
            _textLabel1.font = [UIFont systemFontOfSize:16.0 weight:UIFontWeightMedium];
        }
        return _textLabel1;
    }
    
    /// 动态更换颜色的具体实现
    + (UIColor *)ww_colorWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor {
    #if __IPHONE_13_0
        if (@available(iOS 13.0, *)) {
            return [self colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
                    switch (traitCollection.userInterfaceStyle) {
                        case UIUserInterfaceStyleDark:
                            return darkColor ?: lightColor;
                        case UIUserInterfaceStyleLight:
                        case UIUserInterfaceStyleUnspecified:
                        default:
                            return lightColor;
                    }
                }];
            } else {
    #endif
            return lightColor;
    #if __IPHONE_13_0
            }
    #endif
    }
    
    1.1.3 在assets里面添加动态颜色资源

    在Xcode11里面可以把颜色当作一种资源添加到assets里面,一个颜色组可以有多个颜色,适配不同的主题模式。


    image.png
    - (UILabel *)textLabel2 {
        if (!_textLabel2) {
            _textLabel2 = [[UILabel alloc] initWithFrame:CGRectMake(37, 50 + kLabelHeight *2 + 20 *2, kLabelWidth, kLabelHeight)];
            _textLabel2.backgroundColor = [UIColor lightGrayColor];
            _textLabel2.layer.cornerRadius = 30.0;
            _textLabel2.clipsToBounds = YES;
            _textLabel2.text = @"在Assets中添加动态资源";
            _textLabel2.textAlignment = NSTextAlignmentCenter;
            if (@available(iOS 13.0, *)) {
                _textLabel2.textColor  = [UIColor colorNamed:@"labelColor"];
            }else {
                _textLabel2.textColor = [UIColor redColor];
            }
            _textLabel2.font = [UIFont systemFontOfSize:16.0 weight:UIFontWeightMedium];
        }
        return _textLabel2;
    }
    
    1.2 图片的适配
    1.2.1 在assets中添加动态图片资源
    image.png

    在Xcode11里面,assets里面的一张图片除了根据scale分成三张外,还要根据主题分成三组,如果再根据是否是高对比度、颜色色域、布局方向,这么算下来配置一张图片就需要:33222 = 72张图片资源。

    - (UIImageView *)imageView1 {
        if (!_imageView1) {
            _imageView1 = [[UIImageView alloc] initWithFrame:CGRectMake(150, 50 + kLabelHeight *3 + 20 *3, kSmallImageViewWH, kSmallImageViewWH)];
            _imageView1.image = [UIImage imageNamed:@"image"];
        }
        return _imageView1;
    }
    
    1.2.2 创建自己的动态图片
    - (UIImageView *)imageView2 {
        if (!_imageView2) {
            _imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(196, 50 + kLabelHeight *3 + 20 *3, kSmallImageViewWH, kSmallImageViewWH)];
            _imageView2.image = [UIImage ww_imageWithLightImage:[UIImage imageNamed:@"image2_light"] darkImage:[UIImage imageNamed:@"image2_dark"]];
        }
        return _imageView2;
    }
    
    /// 创建动态图片的具体实现
    + (UIImage *)ww_imageWithLightImage:(UIImage *)lightImage darkImage:(UIImage *)darkImage {
        if (!lightImage) {
            return nil;
        }
    #if __IPHONE_13_0
        if (@available(iOS 13.0, *)) {
            UITraitCollection *lightCollection = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];
            UITraitCollection *darkCollection = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
            UITraitCollection *unspecifiedCollection = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];
            UIImage *image = UIImage.new;
            UIImage *darkPure = [darkImage.imageAsset imageWithTraitCollection:unspecifiedCollection];
            UIImage *lightPure = [lightImage.imageAsset imageWithTraitCollection:unspecifiedCollection];
            [image.imageAsset registerImage:lightPure withTraitCollection:lightCollection];
            [image.imageAsset registerImage:darkPure withTraitCollection:darkCollection];
            [image.imageAsset registerImage:lightPure withTraitCollection:unspecifiedCollection];
            return image;
        } else {
    #endif
            return lightImage;
    #if __IPHONE_13_0
        }
    #endif
    }
    
    1.2.3 网络图片

    比如有这么一种场景:我浅色主题和深色主题下显示不同的图片,并且两张图片都不是本地的,都是从网络获取,实现思路大概是:先把两张图片下下来,在本地组装成动态图片

    - (UIImageView *)imageView3 {
        if (!_imageView3) {
            _imageView3 = [[UIImageView alloc] initWithFrame:CGRectMake(45, 50 + kLabelHeight *3 + 20 *4 + kSmallImageViewWH, 280, kImageView3H)];
            _imageView3.backgroundColor = [UIColor lightGrayColor];
            _imageView3.layer.cornerRadius = 10.0;
            _imageView3.clipsToBounds = YES;
            
        }
        return _imageView3;
    }
    
    /// 具体实现方案
    + (void)ww_imageWithLightUrl:(NSURL *)lightUrl darkUrl:(NSURL * _Nullable)darkUrl completion:(void(^)(UIImage * _Nullable image, NSError * _Nullable error))completion {
        __block BOOL darkFinish = false;
        __block BOOL lightFinish = false;
        __block UIImage *lightImage;
        __block UIImage *darkImage;
        __block NSError *downloadError;
        void(^finishBlock)(void) = ^() {
            if (lightImage && darkImage) {
                UIImage *image = [UIImage ww_imageWithLightImage:lightImage darkImage:darkImage];
                completion(image, nil);
                return;
            }
            NSError *noImageError = [NSError errorWithDomain:@"com.WWDarkModeDemo.Remote" code:0 userInfo:@{@"message": @"图片为nil,请检查你的图片."}];
            completion(nil, noImageError);
           };
           finishBlock = [finishBlock copy];
        
           if (lightUrl == nil || darkUrl == nil) {
                NSError *error = [[NSError alloc] initWithDomain:@"com.WWDarkModeDemo.Extension" code:0 userInfo:@{@"message":@"浅色主题或深色主题的url不能为nil."}];
                completion(nil,error);
            }
        
          [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:lightUrl completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
              lightFinish = YES;
              downloadError = error;
              lightImage = image;
              finishBlock();
          }];
        
          [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:darkUrl completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
               darkFinish = YES;
               downloadError = error;
               darkImage = image;
               finishBlock();
          }];
    }
    
    2.1 UITraitCollection是什么?

    UITraitCollection是用来处理苹果手机的一些特性的存储和UI相关的配置 比如我修改了某些系统设置,如:改字体大小

    + (UITraitCollection *)traitCollectionWithPreferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory API_AVAILABLE(ios(10.0));
    @property (nonatomic, copy, readonly) UIContentSizeCategory preferredContentSizeCategory API_AVAILABLE(ios(10.0)); // unspecified: UIContentSizeCategoryUnspecified
    

    关于主题模式切换的属性是我们本节关注的重点:

    typedef NS_ENUM(NSInteger, UIUserInterfaceStyle) {
        UIUserInterfaceStyleUnspecified,
        UIUserInterfaceStyleLight,
        UIUserInterfaceStyleDark,
    } API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos);
    
    + (UITraitCollection *)traitCollectionWithUserInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos);
    @property (nonatomic, readonly) UIUserInterfaceStyle userInterfaceStyle API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos); // unspecified: UIUserInterfaceStyleUnspecified
    
    2.2 通过子类重写traitCollectionDidChange这个方法来监听系统设置的一些属性变化
    - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
        [super traitCollectionDidChange:previousTraitCollection];
    //    NSLog(@"previousTraitCollection - %ld \n self.traitCollection - %ld",previousTraitCollection.userInterfaceStyle,self.traitCollection.userInterfaceStyle);
        if (@available(iOS 13.0, *)) {
            if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
                if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
                    self.textLabel.textColor = [UIColor orangeColor];
                }else {
                    self.textLabel.textColor = [UIColor yellowColor];
                }
            }
        }
        
    //    UIColor *labelTextColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
    //        if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
    //            return [UIColor orangeColor];
    //        } else {
    //            return [UIColor yellowColor];
    //        }
    //    }];
    //
    //    self.textLabel.textColor = labelTextColor;
    }
    

    三、Q&A

    Q:如何让应用不支持暗黑模式?
    A:info.plist里面添加属性User Interface Stylelight

    Q:如何让应用的某些页面不支持暗黑模式?
    A:

    // 设置当前页面的主题模式 就不跟随系统设置模式的变化而变化
     self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
    

    Q:系统主题更换之后会调用哪些方法?
    A:
    在ViewController:
    - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
    - (void)updateViewConstraints
    - (void)viewWillLayoutSubviews
    - (void)viewDidLayoutSubviews

    在View里:
    - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
    - (void)drawRect:(CGRect)rect
    - (void)layoutSubviews
    - (void)updateConstraints
    - (void)tintColorDidChange

    在UIPresentationController里:
    - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
    - (void)containerViewWillLayoutSubviews
    - (void)containerViewDidLayoutSubviews

    四、结尾

    最好的资料在官方文档:

    https://developer.apple.com/documentation/xcode/supporting_dark_mode_in_your_interface?changes=latest_minor

    WWDC视频:https://developer.apple.com/videos/play/wwdc2019/214

    相关文章

      网友评论

          本文标题:关于iOS13 DarkMode的适配那些事儿

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