目录
深色模式
针对深色模式的推出,Apple官方推荐所有三方App尽快适配。目前并没有强制App进行深色模式适配。因此深色模式适配范围现在可采用以下三种策略:
-
全局关闭深色模式
-
指定页面关闭深色模式
-
全局适配黑暗模式
全局关闭深色模式
方案一:在项目 Info.plist 文件中,添加一条内容,Key为 User Interface Style,值类型设置为String并设置为 Light 即可。
方案二:代码强制关闭黑暗模式,将当前 window 设置为 Light 状态。
if(@available(iOS 13.0,*)){
self.window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}
指定页面关闭深色模式
从Xcode11、iOS13开始,UIViewController与View新增属性 overrideUserInterfaceStyle,若设置View对象该属性为指定模式,则强制该对象以及子对象以指定模式展示,不会跟随系统模式改变。
设置 Window 该属性,将会影响窗口中的所有内容都采用该样式,包括根视图控制器和在该窗口中显示内容的所有控制器
设置 ViewController 该属性,将会影响视图控制器的视图以及子视图控制器都采用该模式。
设置 View 该属性,将会影响视图及其所有子视图采用该模式。
全局适配黑暗模式
一、iPhone提供两种方式设置手机当前外观模式
设置 –> 显示与亮度
控制中心, 长按亮度调节按钮
二、获取当前模式
各种枚举值
typedef NS_ENUM(NSInteger, UIUserInterfaceStyle) {
UIUserInterfaceStyleUnspecified,
UIUserInterfaceStyleLight,
UIUserInterfaceStyleDark,
} API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos);
获取方式一:
if (@available(iOS 13.0, *)) {
UIUserInterfaceStyle mode = UITraitCollection.currentTraitCollection.userInterfaceStyle;
if (mode == UIUserInterfaceStyleDark) {
NSLog(@"深色模式");
} else if (mode == UIUserInterfaceStyleLight) {
NSLog(@"浅色模式");
} else {
NSLog(@"未知模式");
}
}
获取方式二:
-(void)getCurrentMode2{
if(@available(iOS 13.0, *)){
UIUserInterfaceStyle mode = self.traitCollection.userInterfaceStyle;
if (mode == UIUserInterfaceStyleDark) {
NSLog(@"深色模式");
} else if (mode == UIUserInterfaceStyleLight) {
NSLog(@"浅色模式");
} else {
NSLog(@"未知模式");
}
}
}
那么UITraitCollection.currentTraitCollection与self.traitCollection有什么区别呢?
UITraitCollection.currentTraitCollection是从父控制器或父视图传递过来的taitCollection,而self.traitCollection是自身的traitCollection。举个例子window->navigationController->tabController->viewController.在这个序列上各个控制器的UITraitCollection.currentTraitCollection是相同的都是window的traitColllection.但各自的self.traitCollection各自拥有并不相同。
另外这两个traitCollection都是一个动态分配的在不同的模式下其地址是不同的。
三、强行设置App模式
当系统设置为Light Mode时,对某些App的个别页面希望一直显示Dark Mode下的样式,这个时候就需要强行设置当前ViewController的模式了
// 设置当前view或viewCongtroller的模式
@property(nonatomic) UIUserInterfaceStyle overrideUserInterfaceStyle;
if (@available(iOS 13.0, *)){
self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
}
当我们强行设置当前viewController为Dark Mode后,这个viewController下的view都是Dark Mode,由这个ViewController present出的ViewController不会受到影响,依然跟随系统的模式.
要想一键设置App下所有的ViewController都是Dark Mode,请直接在Window上执行overrideUserInterfaceStyle,对window.rootViewController强行设置Dark Mode也不会影响后续present出的ViewController的模式.
if (@available(iOS 13.0, *)){
[self.window setOverrideUserInterfaceStyle:UIUserInterfaceStyleLight];
}
四、监听模式切换
有时我们需要监听系统模式的变化,并作出响应,那么我们就需要在需要监听的viewController中,重写下列函数
//7、监听模式改变(系统调用)
-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection{
//需要调用父类方法
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
NSLog(@"[lilog]:模式发生改变");
}
if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
NSLog(@"[lilog]-监听:深色模式");
}else{
NSLog(@"[lilog]-监听:浅色模式");
}
}
}
当系统模式切换,或当前控制器的模式切换时会调用该方法,比如执行下面代码:
self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
五、颜色适配
iOS13 之前 UIColor只能表示一种颜色,而从 iOS13 开始UIColor是一个动态的颜色,在Light Mode和Dark Mode可以分别设置不同的颜色。
@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方法。
-(void)colorAdaper{
UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(20, 200, 200, 100)];
label.text = @"测试模式";
self.label = label;
if(@available(iOS 13.0, *)){
label.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor blueColor];
}else{
return [UIColor greenColor];
}
}];
label.textColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor blackColor];
}else{
return [UIColor whiteColor];
}
}];
}else{
label.backgroundColor = [UIColor greenColor];
label.textColor = [UIColor greenColor];
}
[self.view addSubview:label];
}
当系统切换模式时,会回调到自定义动态颜色的回调里更改颜色显示。
UIColor能够表示动态颜色,但是CGColor依然只能表示一种颜色,那么对于CALayer等对象如何适配暗黑模式呢?富文本如何适配深色模式?
//监听模式改变(系统调用)
-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection{
//需要调用父类方法
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
//CGColor的适配
UIColor * borderColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor yellowColor];
}else{
return [UIColor purpleColor];
}
}];
_label.layer.borderWidth = 3;
//_label.layer.borderColor = [borderColor resolvedColorWithTraitCollection:self.traitCollection].CGColor;
_label.layer.borderColor = borderColor.CGColor;
//富文本的适配
NSDictionary * dic = @{NSForegroundColorAttributeName:borderColor,
NSFontAttributeName : [UIFont fontWithName:@"Helvetica-Bold" size:17]};
[_label setTitleTextAttributes:dic];
}
}
六、图片适配
在xx.xcassets文件内新建一个Image set,
打开右侧工具栏,点击最后一栏,找到Appearances,选择Any,Dark
将两种模式下不同的图片资源都拖进去
使用该图片
[_logoImage setImage:[UIImage imageNamed:@"icon_logo"]];
工程适配思路
对现有工程如何适配的几点思考:
1、简单适配原则,不需要产品另外提供一套深色模式下的色值和图片。
iOS13下一些系统的view控件是支持在不同模式下显示不同的背景色的。我们可以默认使用系统提供的颜色。将自己的工程运行起来,切换不同的模式,根据不同的页面显示效果,发现哪里显示有问题就改哪里,进行局部修改。这样做能快速适配暗黑模式,而不至于使APP在暗黑模式下显得很奇怪。缺点是不够定制化,适配的效果不够完美。
2、深度适配,让产品提供深色模式下的色值和图片。根据上边的适配方法进行整体精致化的适配。
适配之前建议搞一个全局的颜色管理器,用来管理不同模式下的不同颜色,这样方便以后进行更改。同时有一点好处是假如以后要加入主题实现起来也是很方便的。
3、关于weex适配暗黑模式的思考。
原生端适配还是比较简单的,但对一些采用了weex工程的APP来说怎么适配呢?现在能想到的方案:
方案一: 由于weex最终会渲染成原生控件,所以跟上面提到的简单适配原则一样,哪里有问题改哪里。
方案二: 通过切换模式的回调内发出通知,weex工程内监听通知获知当前所处模式,进行颜色和图片的切换。
4、原生与weex统一适配方案
在适配暗黑模式的尝试过程中,与同事进行探讨,能不能在不改动weex代码的情况下来实现较好的适配效果?
由于weex最终都会渲染成原生控件所以我们从下面几个方面入手:
第一:同样要有一个颜色管理,比如创建一个plist文件,浅色模式的颜色值做key,深色模式的颜色值做value。
第二:给UIView、UILable、UIButton、… 添加分类,在分类里对设置背景色,字体颜色,高亮颜色,… 利用runtime进行交换,在自定义的方法内部设置动态颜色。
第三:关于CGColor的适配,暂时还未找到可行的统一处理的方法,有一个思路就是在UIView的分类里监听深色浅色模式回调,同时通过kvo监听view.layer.color是否有赋值,代码如下:
@interface UIView()
@property (nonatomic, strong) UIColor * lightColor;//
@property (nonatomic, strong) UIColor * darkColor;//
@end
@implementation UIView (LKLThemeColor)
-(UIColor *)lightColor{
return objc_getAssociatedObject(self, _cmd);
}
-(void)setLightColor:(UIColor *)lightColor{
return objc_setAssociatedObject(self, @selector(lightColor), lightColor,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(UIColor *)darkColor{
return objc_getAssociatedObject(self, _cmd);
}
-(void)setDarkColor:(UIColor *)darkColor{
return objc_setAssociatedObject(self, @selector(darkColor), darkColor,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//在设置frame的时候添加kvo监听
- (void)lkl_setFrame:(CGRect)frame{
[self lkl_setFrame:frame];
[self.layer addObserver:self forKeyPath:@"borderColor" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context{
// self.recordColor1 = [UIColor colorWithCGColor:(__bridge CGColorRef)change[@"new"]];
//这里需要根据plist文件内色值的获取对浅色和深色分别赋值,简单举个例子就直接写死了。
self.lightColor = [UIColor redColor];
self.darkColor = [UIColor yellowColor];
if (@available(iOS 13.0, *)) {
//CGColor的适配
UIColor * borderColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return self.darkColor;
}else{
return self.lightColor;
}
}];
self.layer.borderColor = borderColor.CGColor;
}
}
+ (void)load{
Method method1 = class_getInstanceMethod([self class], @selector(setFrame:));
Method method2 = class_getInstanceMethod([self class], @selector(lkl_setFrame:));
method_exchangeImplementations(method1, method2);
}
//监听模式改变(系统调用)
-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection{
//需要调用父类方法
// [super traitCollectionDidChange:previousTraitCollection];
if (self.lightColor&&self.darkColor) {
if (@available(iOS 13.0, *)) {
//CGColor的适配
UIColor * borderColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return self.darkColor;
}else{
return self.lightColor;
}
}];
self.layer.borderColor = borderColor.CGColor;
}
}
}
@end
但是这样有一个问题就是当类内,比如UILabel重载了下面的方法后UILabel的颜色变换就会失效
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;
但是这样也解决了大部分的CGColor的适配,剩下的重载了上面两个方法的类在单独处理下工作量也不大。
行者常至,为者常成!