iOS 暗黑模式适配(项目实战篇)

通过上面两篇文章
iOS 暗黑模式适配(方法介绍篇)
iOS 暗黑模式适配(叨叨篇)
我们知道了暗黑模式怎么去适配,那么如何结合到真实的项目场景中去呢?

适配目标

在项目适配之前,先来梳理一下需要适配的东西,以及可能预见的问题。

一、下图是我们单个App的控件适配目标,主要目标就是根据不同模式适配不同的背景颜色和图片资源

image.png

二、如何方便统一管理适配颜色,统一风格
三、界面过多,发版间隙短,可能一次性适配不完
四、有些界面可能来不及适配或者不适合显示成暗黑模式<比如打印页面>,对于这种特殊的界面如何处理
五、给用户提供自主选择的权利,可以在 App 内手动关闭暗黑模式
六、如何适配网络图片?比如我们一个界面有两套主题,主题的图片是从网络加载的,就没法使用系统提供的
Assets.xcassets方法了。
七、如何方便的适配CALayer的背景颜色和边框颜色
八、启动页怎么进行适配
九、Web页RN怎么去适配

问题解决:

一、主要目标就是根据不同模式适配不同的背景颜色和图片
二、如何方便统一管理适配颜色,统一风格

在我们的App中适配的颜色可能有很多
App基色调,文字的标题颜色,副标题的颜色,分割线颜色,内容背景颜色等等
我们在适配的时候如果每一个需要适配的控件都去写UIColor的那一段代码,可能太臃肿。为了对颜色统一管理,我们可以使用配置文件的形式进行适配。语义颜色的形式进行适配。

未命名文件 (2).png

颜色配置文件.png

项目启动将配置文件中的色值加载到内存中,通过key的形式获取到对应的UIColor<暗黑模式和其他模式>。直接使用即可

label.textColor = [JMIShowStyleLib colorWithType:@"textColor"];

如果想更方便,也可以写成宏定义,因为一个App中对于一类控件的显示就是那么几个颜色,重复利用率还是很高的。

// 普通label的颜色
#define KShowStyleTextColor [JMIShowStyleLib colorWithType:@"textColor"]
label.textColor = KShowStyleTextColor;

这样设计的好处:

1、每一个ShowStyleBaseLib的子类都对应一个单独的Plist文件,这是为了方便其他的子库使用更加方便,不受主项目的影响。这样也不会受到命名冲突的影响,因为在不同的文件中。
2、暗黑模式是13以后出来的,那么之前的版本我们也是需要适配的,用这种方式,可以统一进行版本逻辑判断

/// 通过类型获取color
/// @param typeStr 对应相关配置plist中的颜色配置
+ (UIColor *)colorWithType:(NSString *)typeStr {
    if (![typeStr isKindOfClass:[NSString class]]) return nil;
    
    JMIShowStyleModel *model = [[self share].configModelDic objectForKey:typeStr];
    UIColor *color = nil;
    // 获取lightColor
    UIColor *lightColor = model.lightColor;
    if (@available(iOS 13.0, *)) {
        // 获取darkColor
        UIColor *darkColor = model.darkColor;
        // 获取最终颜色
        color = [UIColor colorWithLightColor:lightColor darkColor:darkColor];
    } else {
        color = lightColor;
    }
    return color;
}

4、通过Plist文件的形式进行颜色列举更加清晰,方便修改,管理。后期也可以添加后台下发配置文件的逻辑。

三、界面过多,发版间隙短,可能一次性适配不完
我们可能在一个版本中适配不完所有的界面,可能得需要多个版本才能把所有的界面适配完毕。
1、可以在开发阶段,将Info.plist中的模式配置删掉,保证正常开发,上线之前再加上Light的配置。直到适配完成,再删除Info.plist中的模式配置
2、但是注意,某个 VC或者View模式不要写死UIUserInterfaceStyleDark

self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;

四、项目中有些界面可能来不及适配或者不适合暗黑模式<比如打印页面>
对于有些来不及适配的界面可能在比较小型的项目中出现几率不大,
但是对于多业务线集成一个项目的可能会出现,每个业务线排期时间不一致,导致暗黑模式适配后置。

产生这种尴尬的原因:
系统默认给一些UI进行了适配,就会出现有些控件是暗黑,有些不是暗黑,显示可能会很尴尬。

image.png

如果设置当前页面为UIUserInterfaceStyleLight可以避免这个尴尬,让没有准备好的界面还保持未适配前的状态。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    if (@available(iOS 13.0, *)) {
        self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
    } 
}

我们可能没法通知各个业务线让他们给每个没适配的VC加上这个东西。
解决办法:

1、使用黑魔法runtime,在VC的viewDidLoad中判断除了白名单VC<系统VC,带下划线的VC等等>默认设置为UIUserInterfaceStyleLight。

- (void)jmi_viewDidLoad {
    if (@available(iOS 13.0, *)) {
        // 除了特殊的VC之外,默认将所有的VC的 userInterfaceStyle 变为Light
        if(![self isSystemController]){
            NSLog(@"----%@",[self class]);
            self.userInterfaceStyle = JMIUserInterfaceStyleLight;
        }
    }
    [self jmi_viewDidLoad];
}

// 当前的VC是否是需要过滤的VC
- (BOOL)isSystemController {
    NSArray *array = @[@"UI",@"_"];
    NSString *name = [NSString stringWithFormat:@"%@",[self class]];
    BOOL isSystem = NO;
    for (NSInteger i = 0; i < array.count; i++) {
        NSString *subStr = array[i];
        if ([name rangeOfString:subStr].location == 0 && [name rangeOfString:subStr].length > 0) {
            isSystem = YES;
            break;
        }
    }
    return isSystem;
}

2、在想要适配暗黑模式的VC,viewDidLoad方法中,再将VC的模式变为UIUserInterfaceStyleUnspecified

@interface AViewController ()
@end
@implementation AViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    if (@available(iOS 13.0, *)) {
        self.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
    }
}

这样的话,不影响其他业务线,也不影响要开发的业务线,等着全部业务线都适配完毕,可以考虑全局查找统一删除。

3、暗黑跳转非暗黑界面
对未能适配暗黑模式的VC,显示倒是正常了,但是当用户从一个黑色背景页面跳转到白色背景页面的时候,可能会反差过大。
那么我们可以考虑给window添加一层没有相应的蒙层。
需要考虑这么几个问题:
3.1、如何判断当前VC是否要添加蒙层
当前App如果处于暗黑模式下。
使用runtime在VC的viewWillAppear中判断是不是UIUserInterfaceStyleLight,如果是,那么我们展示这层蒙版。

- (void)jmi_viewWillAppear:(BOOL)animated {
    if (@available(iOS 13.0, *)) {
        UIUserInterfaceStyle windowUserInterfaceStyle = [UIApplication sharedApplication].delegate.window.traitCollection.userInterfaceStyle;
        if (windowUserInterfaceStyle == UIUserInterfaceStyleDark &&  self.userInterfaceStyle == JMIUserInterfaceStyleLight) {
            [JMIShowCoverageManager showCoverage];
        }
    }
    [self jmi_viewWillAppear:animated];
}

- (void)jmi_viewDidAppear:(BOOL)animated {
    if (@available(iOS 13.0, *)) {
        if (self.userInterfaceStyle != JMIUserInterfaceStyleLight) {
            [JMIShowCoverageManager hiddenCoverage];
        }
    }
    [self jmi_viewDidAppear:animated];
}

3.2、添加蒙层的方式
选择在最下层的window上添加一层View,并让这个View显示在最上层,简单暴力。

/// 在所有页面之上展示一层阴影
+ (void)showCoverage {
    UIWindow *window = [UIApplication sharedApplication].delegate.window;
    UIView *coverageView = [window viewWithTag:-999];
    if (!coverageView) {
        coverageView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, JMIMainW, JMIMainH)];
        coverageView.backgroundColor = JMIColorAlpha(0x000000, 0.5);
        coverageView.tag = -999;
        // 保证显示在最上面
        coverageView.layer.zPosition = 9999;
        coverageView.userInteractionEnabled = NO;
        [window addSubview:coverageView];
    } else {
        coverageView.hidden = NO;
    }
}

/// 隐藏最上层展示的阴影
+ (void)hiddenCoverage {
    UIWindow *window = [UIApplication sharedApplication].delegate.window;
    UIView *coverageView = [window viewWithTag:-999];
    coverageView.hidden = YES;
}

3.3、隐藏时机
使用runtimeviewDidAppear检查如果不是UIUserInterfaceStyleLight那么隐藏蒙层

监听当前VC模式的变化,进而修改蒙层的显示和隐藏<展示当前VC,修改系统的展示模式>

最终的效果图.gif

五、开关切换,App是否跟随系统模式变化而变化

image.png

对于这个功能,我们只需要设置windowoverrideUserInterfaceStyle属性即可,把最底层的设置完毕之后,上层的也会收到影响。

if (@available(iOS 13.0, *)) {
    UIWindow *window = [UIApplication sharedApplication].delegate.window;
    // 设置为不跟随系统变化
    window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}
if (@available(iOS 13.0, *)) {
    UIWindow *window = [UIApplication sharedApplication].delegate.window;
    // 设置为跟随系统变化
    window.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
}

但是还有一个未解决的问题,就是怎么把启动页的模式修改成Light。
看了下其他的APP,要么没有这个功能,要么启动页看不出是Dark还是Light

六、如何适配网络图片?比如我们一个界面有两套主题,主题的图片是从网络加载的,就没法使用系统提供的Assets.xcassets方法了。

大概的逻辑是:
1、初始化图片的时候根据imageView.traitCollection.userInterfaceStyle判断当前的模式,根据当前的模式设置图片
2、在- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection中再去根据当前模式设置图片
为了方便,可以封装成分类的形式
使用runtime拦截traitCollectionDidChange,然后在其中刷新Image

UIImage *lightImage = [UIImage imageNamed:@"Rainbow_1"];
UIImage *darkImage = [UIImage imageNamed:@"Sunset_3"];
[self.localImageView1 setImageWithLightImage:lightImage darkImage:darkImage];
// 设置网络图片
NSURL *lightImageUrl = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590472896956&di=74ad720be53af171c0289ce65abbc2f7&imgtype=0&src=http%3A%2F%2Fimg.mp.itc.cn%2Fq_70%2Cc_zoom%2Cw_640%2Fupload%2F20170522%2Fdf59dfe5a47848f08b9e8d045e29d79f_th.jpg"];
NSURL *darkImageUrl = [NSURL URLWithString:@"http://hbimg.b0.upaiyun.com/8b6e163d2151e6925b28093d763ddd4f9ce11efa508bb-8XV2nw_fw658"];
[self.netImageView1 setImageWithLightImageUrl:lightImageUrl darkImageUrl:darkImageUrl placeholderImage:[UIImage imageNamed:@"default_rect"]];

七、如何方便的适配CALayer的背景颜色和边框颜色
CALayer 不能通过UIColor提供的颜色进行直接适配
他需要跟随他所在的superLayer的View上的userInterfaceStyle进行变化
为了方便可以封装成Layer的分类,只需两步即可完成操作

// 初始化layer的背景颜色
_showLayer.backgroundColor = [JMILayerShowStyleLib getShowStyleModelWithType:@"layerBackColor"].lightColor.CGColor;

需要手动在layer所在的View中的回调中刷新

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
    [super traitCollectionDidChange:previousTraitCollection];
    if (@available(iOS 13.0, *)) {
        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
            // 设置layer的背景颜色
            [self.showLayer setBackgroundColorWithStyleModel:[JMILayerShowStyleLib getShowStyleModelWithType:@"layerBackColor"]];
        }
    }
}

边框颜色类似

八、启动页怎么进行适配
之前的时候我们使用的是LaunchImage的形式进行设置的
苹果要去2020年6月30日之后不使用LaunchScreen.storyboard的形式设置的话,会不让上架。
只能朝 LaunchScreen.storyboard 下手了。
1、使用系统提供的颜色设置背景
2、Assets.xcassets中配置不同图片给ImageView

image.png

九、Web页RN怎么去适配

网页适配
1、我们可以通过添加阴影遮罩的形式去适配
2、加载的时候可以通过JS交互或者参数传递给Web页面,当状态发生变化的时候,通过JS交互告诉Web

RN适配
1、我们可以通过添加阴影遮罩的形式去适配
2、可以通过协议跳转参数进行状态传递,并且传递样式颜色等,当状态发生变化的时候,RN模块进行刷新。

最后是Demo地址

参考文档:
iOS 13,暗黑模式不是所有产品都适合
https://developer.apple.com/documentation/xcode/supporting_dark_mode_in_your_interface?changes=latest_minor&language=objc
京东 App适配 iOS 暗黑模式业务实践
“暗黑模式”之58 同城 iOS App深色模式适配实践

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