iOS-使用QMUITheme实现换肤并适应iOS 13黑暗模式

热门标签

,

特别声明:文章多为网络转载,资源使用一般不提供任何帮助,特殊资源除外,如有侵权请联系!

 

背景

一,iOS 13黑暗模式

iOS 13系统新增了Dark Mode,需要项目自行适应,系统提供的适应方案简述如下:

  1. UIView框架
    1. 对于UIColor,使用UIColor (DynamicColors)里提供的新API去初始化颜色,例如+[UIColor colorWithDynamicProvider:],或者用Xcode 11在资产里为每个颜色创建一个Color Set然后在外观里选择带有Dark的选项来生成一个动态颜色,代码里通过+[UIColor colorNamed:]来获取颜色。iOS-使用QMUITheme实现换肤并适应iOS 13黑暗模式
    2. 对于UIImage,使用Xcode 11在Assets里的外观选择带有Dark的选项来创建一个动态的图片。 iOS-使用QMUITheme实现换肤并适应iOS 13黑暗模式
    3. 对于UIVisualEffect,使用iOS 10之后的UIBlurEffectStyle(例如UIBlurEffectStyleRegular),系统会自动切换Light / Dark样式。
    4. 其他内容可在traitCollectionDidChange:里根据self.traitCollection.userInterfaceStyle的值来判断当前的主题。
  2. UIViewController尺寸,通过在traitCollectionDidChange:里根据self.traitCollection.userInterfaceStyle的值来判断当前的主题。

二,系统适应方案的局限性

  1. 对于UIColor,系统有API生成动态颜色,但CGColor却没有,所以到使用CGColor的地方都需要重写traitCollectionDidChange:在里面重新设置一遍,无法保持代码风格的一致性。
  2. 在App回到后台的时候,系统会分别使用Light Mode和Dark Mode为App当前界面截两张图,从而保证你切换了主题后再唤醒App,不会看到颜色跳变的过程,保证了体验,,但由于CALayer带有隐式动画,在截图时UIView已经渲染完新样式了,CALayer还是旧的,从而表现出明显的UI问题(注意下图最终重启App后,色块(CALayer)能看到从白变黑的过程,而界面上其他地方的颜色是看不到变化过程的)。iOS-使用QMUITheme实现换肤并适应iOS 13黑暗模式
  3. 对于UIColorUIImage这些对象,如果你将其使用UIView.backgroundColorUIView.tintColor这种系统原生自带的属性,当设备主题发生切换时,UIView会自动去刷新这些值(因为它知道有这些值需要刷新),但如果你是一个自定义视图里的自定义属性(例如QMUIGridView.separatorColor),系统并不知道你总共有任何属性需要更新,所以你依然需要进行traitCollectionDidChange:来重新设置一遍,导致UI代码需要分散到多个地方,不好维护。
  4. 从开发者的角度,让一个项目兼容iOS 13 Dark Mode,和让项目在所有iOS版本下都能支持Dark Mode,这两者的工作量相差不大,都是要对现有的UI代码做大量修改。如果要兼容iOS 13黑暗模式,最大的收益肯定是同时支持所有iOS版本,而系统提供的方案并不考虑iOS 12及以下版本的实现。

三,QMUI提供的方案

基于以上状况,我们设计了QMUITheme组件,它解决的问题是:

  1. 支持全iOS版本的换肤,可设置多个主题。
  2. 兼容iOS 13 Dark Mode,可将Dark Mode映射为App中的某一个主题,对业务而言只需要关心业务主题,不需要关心设备当前是否开启了iOS 13 Dark Mode。
  3. 支持UIColorCGColorUIImageUIVisualEffect的动态化,在视图初始化的时候直接使用这些动态对象即可,不需要强制写在某些updateXxx,xxxDidChange的方法里,以保持最优雅的编码风格。iOS-使用QMUITheme实现换肤并适应iOS 13黑暗模式
  4. 支持UIKit自带的视图组件和业务自定义视图组件里与颜色,图片相关属性的自动刷新。

QMUITheme使用教程

拥有5个主题,其中“ Dark”主题对应iOS 13 Dark Mode。在iOS 13下,每次QMUI Demo启动时都会根据当前设备是否开启了Dark模式,来强制将QMUI Demo的主题设置为“默认”或“黑暗”。

一,基本概念解释

  1. QMUI主题管理器同时在iOS 13下它也负责监听系统Dark Mode的切换,变为其映射为已注册的某个业务主题。
  2. QMUI主题管理中心管理QMUIThemeManager的工具,QMUIThemeManager也只有通过QMUIThemeManagerCenter才能生成特定实例的。主要用于实现一个项目里存在多个维度的主题的场景。对于只需要一个维度的主题的项目而言,任何时候都使用QMUIThemeManagerCenter.defaultThemeManager获取manager实例即可。
  3. 主题(theme)对项目而言,主题可以是任意的对象类型,存在多个主题时,不同主题也可以是不相同的类型,甚至可以是一个无意义仅占位用的NSNull。颜色,图片等信息。在QMUI Demo里,每个主题对应一个QMUI配置表,也即NSObject<QDThemeProtocol> *类型。

    QMUIThemeManager主题,一个主题对应一个唯一的identifier。与主题类似,标识符对类型也没有要求,只需要支持NSCopying协议即可,所以可以用NSStringNSNumber等常见类型,怎么方便怎么来。

  4. 动态对象换肤的直接操作对象通常只有UIColor(包含CGColor), UIImageUIVisualEffect这三种,在以前,你当拿到一个UIColor对象,它对应什么实体的色值,是确定的,但在13的iOS或QMUITheme体系下,同一个color对象在不同的​​主题下可能会展示出不同的色值,于是这种对象我们称为“动态对象”。动态对象均需要以特定的方式来创建,例如:
    //创建一个动态颜色 
    UIColor * dynamicColor = [UIColor qmui_colorWithThemeProvider: ^ UIColor * _Nonnull(__kindof QMUIThemeManager * _Nonnull Manager, NSString * _Nullable标识符,NSObject <QDThemeProtocol> * theme){
        返回 [identifier isEqualToString:@“ Dark  ]吗?UIColor。blackColor:UIColor。whiteColor ;
    }];
    
    //创建一个动态图片 
    的UIImage *动态图片= [UIImage的 qmui_imageWithThemeProvider: ^的UIImage * _Nonnull(__kindof QMUIThemeManager * _Nonnull经理的NSString * _Nullable标识符,NSObject的<QDThemeProtocol> *主题){
        返回 [UIImage的 imageNamed: [标识符 isEqualToString:@”@“ image_dark  @“ image_default  ];
    }];
    
    //创建一个动态模糊效果 
    UIVisualEffect * dynamicEffect = [UIVisualEffect qmui_effectWithThemeProvider: ^ UIVisualEffect * _Nonnull(__kindof QMUIThemeManager * _Nonnull经理的NSString * _Nullable标识符,NSObject的<QDThemeProtocol> *主题){
        返回 [UIBlurEffect effectWithStyle: [标识符 isEqualToString:@”黑暗 ]吗?UIBlurEffectStyleDark:UIBlurEffectStyleLight];
    }];

二,使用步骤

首先,请先确认您希望以某种形式的形式封装您的主题对象,以QMUI演示为例,每个主题对应一个配置表,每个配置表替换一个NSObject<QDThemeProtocol> *类型。

然后,在提示早的时机初始化QMUIThemeManager,以保证其他使用颜色,图片的地方能获取到正确的值。QMUIDemo中选择的时机是application:didFinishLaunchingWithOptions:

// 1.先注册主题监听器,在另一端将主题持久化存储(存储到数据库或NSUserDefaults),避免启动过程中主题发生变化时读取到错误的值 
[[ NSNotificationCenter  defaultCenter ] addObserver:自我 选择器:@选择器 handleThemeDidChangeNotification: 名称: QMUIThemeDidChangeNotification对象: ]。

// 2.然后设置用于生成主题的块,在需要的时候QMUIThemeManager会通过这个块得到一个主题对象 
QMUIThemeManagerCenter.defaultThemeManager.themeGenerator = ^ __ kindof NSObject的* _Nonnull的NSString * _Nonnull标识符){
    如果([标识符 isEqualToString:@“默认 ])返回 QMUIConfigurationTemplate。新的 ;
    如果([identifier isEqualToString:@“ Dark  ])返回 QMUIConfigurationTemplateDark。新的 ;
    返回 nil ;
};

// 3.再针对iOS 13开启自动响应系统的Dark Mode切换
//如果不需要兼容iOS 13 Dark Mode,则不需要这
一段代码if(@available(iOS 13.0,*)){
     //先通过这个block来决定当系统的Dark Mode发生切换时,要如何映射到业务的主题 
    QMUIThemeManagerCenter。defaultThemeManageridentifierForTrait = ^ __ kindof NSObject的<NSCopying> * _Nonnull(UITraitCollection * _Nonnull性状){
        如果(性状。 userInterfaceStyle == UIUserInterfaceStyleDark){
            返回 @ “黑暗 ; //表示当检测到系统开启了Dark Mode时,将主题自动切换到Dark
        }
        返回 @“默认 ;
    };
    
    //然后让QMUIThemeManager自动响应系统的Dark Mode切换 
    QMUIThemeManagerCenter。defaultThemeManagerresponsesSystemStyleAutomatically = YES ;
}

做完以上的初始化配置后,其余的就是业务界面的适应了,按照上述提及的,将颜色,图像,效果都换成对应的动态对象。

view.backgroundColor = [UIColor qmui_colorWithThemeProvider: ^ UIColor * _Nonnull(__ kindof QMUIThemeManager * _Nonnull管理器,NSString * _Nullable标识符,__kindof NSObject * _Nullable主题){
     返回 [标识符isEqualToString:@“黑暗 ]?UIColor。blackColor:UIColor。whiteColor ;
}];
layer.backgroundColor = [UIColor qmui_colorWithThemeProvider: ^ UIColor * _Nonnull(__ kindof QMUIThemeManager * _Nonnull管理器,NSString * _Nullable标识符,__kindof NSObject * _Nullable主题){
     返回 [标识符isEqualToString:@“黑暗 ]?UIColor。blackColor:UIColor。whiteColor ;
}]。CGColor ;imageView.image 
= [UIImage qmui_imageWithThemeProvider: ^ UIImage * _Nonnull(__Mindof QMUIThemeManager * _Nonnull Manager,NSString * _Nullable标识符,NSObject <QDThemeProtocol> * theme){
     返回 [UIImage imageNamed: [identifier isEqualToString:@“ Dark  ]吗?@“ image_dark @“ image_default  ];
}];
visualEffectView.effect = [UIVisualEffect qmui_effectWithThemeProvider: ^ UIVisualEffect * _Nonnull(__kindof QMUIThemeManager * _Nonnull经理的NSString * _Nullable标识符,NSObject的<QDThemeProtocol> *主题){
     返回 [UIBlurEffect effectWithStyle: [标识符isEqualToString:@ “黑暗 ]?UIBlurEffectStyleDark:UIBlurEffectStyleLight];
}];

通常来说,一个项目里的颜色,模糊效果一般都只有可枚举的固定的几个,建议将这些颜色,模糊效果缓存起来(使用static变量,或者用一个单例去保存,可参考QDThemeManager),这样可以大大减少代码量,也不需要去记住创建动态对象的语法。

//适当做一些撤除,就可以减少大量的代码
view.backgroundColor = UIColor.qd_backgroundColor;
layer.backgroundColor = UIColor.qd_separatorColor。CGColor ;
visualEffectView.effect = UIVisualEffect.qd_standardBlurEffect;

到此大部分界面已经可以兼容,而对于自定义的视图组件,它们的自定义属性需要在主题切换时被刷新,则可以参照下方来注册属性给QMUIThemeManager。

@实现 CustomView

-(instancetypeinitWithFrame CGRectframe {
     if(self = [ super  initWithFrame: frame]){
         // ...
        
        //将组件里的样式定义为属性,然后通过qmui_registerThemeColorProperties:注册给QMUIThemeManager,这样当主题发生变化时,会遍历整个界面的所有视图,重新设置被注册的属性 
        [ self  qmui_registerThemeColorProperties: @ [ NSStringFromSelector @selector borderColor)),
                                                   NSStringFromSelector @selector contentImage)),
                                                   NSStringFromSelector @selector backgroundEffect))]]];
    }
    返回自我
}

@结束

如果上述提供的功能仍可以满足业务的需求,也可以直接重写UIView / UIViewController的qmui_themeDidChangeByManager:identifier:theme:方法,在内部写自己的逻辑。

至此整个App的主题实现和Dark Mode兼容工作就全部完成了。

以主题的切​​换,如果App只是兼容iOS 13 Dark Mode,则不需要理会这一点,如果App里有提供主动切换主题的操作给用户,则可参照以下代码:

QMUIThemeManagerCenter.defaultThemeManager.currentThemeIdentifier = @“黑暗 ; //切换到深色的主题
//或 
QMUIThemeManagerCenter.defaultThemeManager.currentTheme = darkTheme; //切换到darkTheme主题对象

 

未经允许不得转载:作者:SheaYang, 转载或复制请以 超链接形式 并注明出处 技术Dog|博客
原文地址:《iOS-使用QMUITheme实现换肤并适应iOS 13黑暗模式》 发布于2019-10-18

分享到:
赞(0) 打赏

评论 抢沙发

3 + 8 =


iOS-使用QMUITheme实现换肤并适应iOS 13黑暗模式

长按图片转发给朋友

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

Vieu4.0主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。

登录

忘记密码 ?