关注仓库,及时获得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github

从开始写 DKNightVersion 这个框架到现在已经将近一年了,目前整个框架的设计也趋于稳定。

其实夜间模式的实现就是相当于多主题加颜色管理。而最新版本的 DKNightVersion 已经很好的解决了这个问题。

在正式介绍目前版本的实现之前,我会先简单介绍一下 1.0 时代的 DKNightVersion 的实现,为各位读者带来一些新的思路,也确实想梳理一下这个框架是如何演变的。

我们会以对 backgroundColor 为例说明整个框架的工作原理。

方法调剂的版本

如何在不改变原有的架构,甚至不改变原有的代码的基础上,为应用优雅地添加夜间模式成为很多开发者不得不面对的问题。这也是 1.0 时代的 DKNightVersion 想要实现的目标。

其核心思路就是使用方法调剂修改 backgroundColor 的存取方法

使用 nightBackgroundColor

在思考之后,我想到,想要在不改动原有代码的基础上实现夜间模式只能通过在分类中添加 nightBackgroundColor 属性,并且使用方法调剂改变 backgroundColor 的 setter 方法。

- (void)hook_setBackgroundColor:(UIColor*)backgroundColor {if ([DKNightVersionManager currentThemeVersion] == DKThemeVersionNormal) {[self setNormalBackgroundColor:backgroundColor];}[self hook_setBackgroundColor:backgroundColor];
}

在当前主题为 DKThemeVersionNormal 时,将颜色保存至 normalBackgroundColor 中,然后再调用原 backgroundColor 的 setter 方法,更新视图的颜色。

DKNightVersionManager

这里只解决了颜色设置的问题,下面会说明,如果在主题改变时,实时更新颜色,而不用重新进入当前页面。

整个 DKNightVersion 都是由一个 DKNightVersionManager 的单例来管理的,而它的主要工作就是负责改变应用的主题、并在主题改变时通知其它视图更新颜色

- (void)changeColor:(id <DKNightVersionChangeColorProtocol>)object {if ([object respondsToSelector:@selector(changeColor)]) {[object changeColor];}if ([object respondsToSelector:@selector(subviews)]) {if (![object subviews]) {// Basic case, do nothing.return;} else {for (id subview in [object subviews]) {// recursive darken all the subviews of current view.[self changeColor:subview];if ([subview respondsToSelector:@selector(changeColor)]) {[subview changeColor];}}}}
}

如果主题更新,那么就会递归地调用 changeColor 方法,刷新全部的视图颜色,而这个方法的实现比较简单:

- (void)changeColor {if ([DKNightVersionManager currentThemeVersion] == DKThemeVersionNormal) {self.backgroundColor = self.normalBackgroundColor;} else {self.backgroundColor = self.nightBackgroundColor;}
}

上面就是整个框架在 1.0 版本时的实现思路。不过这个版本的 DKNightVersion 在实际应用中会有比较多的问题:

  1. 在高速滚动的 scrollView 上面来回切换夜间模式,会出现颜色错乱的问题

  2. 由于对 backgroundColor 属性进行不合适的方法调剂,其行为无法预测,比如:在设置颜色后,再取出,不一定与设置时传入的颜色相同

  3. 无法适配第三方 UI 控件

使用色表的版本

为了解决 1.0 中的各种问题,我决定在 2.0 版本中放弃对 nightBackgroundColor 的使用,并且重新设计底层的实现,转而使用更为稳定安全的方法实现夜间模式,先看一下效果图:

新的实现不仅能够支持夜间模式,而且能够支持多主题。

DKColorPicker

与上一个版本实现上的不同,在 2.0 中删除了全部的 nightBackgroundColor,使用一个名为 dk_backgroundColorPicker 的属性取代它。

@property (nonatomic, copy) DKColorPicker dk_backgroundColorPicker;

这个属性其实就是一个 block,它接收参数 DKThemeVersion *themeVersion,但是会返回一个 UIColor *

在第一次传入 picker 或者每次主题改变时,都会将当前主题 DKThemeVersion 传入 picker 并执行,然后,将得到的 UIColor 赋值给对应的属性 backgroundColor 更新视图颜色。

typedef UIColor *(^DKColorPicker)(DKThemeVersion *themeVersion);

比如下面使用 DKColorPickerWithRGB 创建一个临时的 DKColorPicker

  1. DKThemeVersionNormal 时返回 0xffffff

  2. DKThemeVersionNight 时返回 0x343434

  3. 在自定义的主题下返回 0xfafafa (这里的顺序与色表中主题的顺序有关)

cell.dk_backgroundColorPicker = DKColorPickerWithRGB(0xffffff, 0x343434, 0xfafafa);

同时,每一个对象还持有一个 pickers 数组,来存储自己的全部 DKColorPicker

@interface NSObject ()@property (nonatomic, strong) NSMutableDictionary<NSString *, DKColorPicker> *pickers;@end

在第一次使用这个属性时,当前对象注册为 DKNightVersionThemeChangingNotificaiton 通知的观察者。

在每次收到通知时,都会调用 night_update 方法,将当前主题传入 DKColorPicker,并再次执行,并将结果传入对应的属性 [self performSelector:sel withObject:result]

- (void)night_updateColor {[self.pickers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull selector, DKColorPicker  _Nonnull picker, BOOL * _Nonnull stop) {SEL sel = NSSelectorFromString(selector);id result = picker(self.dk_manager.themeVersion);[UIView animateWithDuration:DKNightVersionAnimationDurationanimations:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"[self performSelector:sel withObject:result];
#pragma clang diagnostic pop}];}];
}

也就是说,在每次改变主题的时候,都会发出通知。

DKColorTable

虽然我们在上面临时创建了一些 DKColorPicker。不过在 DKNightVersion 中,我更推荐使用色表,来减少相同的 DKColorPicker 的创建,并且能够更好地管理整个应用中的颜色:

NORMAL   NIGHT    RED
#ffffff  #343434  #fafafa BG
#aaaaaa  #313131  #aaaaaa SEP
#0000ff  #ffffff  #fa0000 TINT
#000000  #ffffff  #000000 TEXT
#ffffff  #444444  #ffffff BAR

上面就是默认色表文件 DKColorTable.txt 中的内容,其中,第一行表示主题,NORMAL 主题必须存在,而且必须为第一列,而最右面的 BGSEP 就是对应 DKColorPicker 的 key。

self.tableView.dk_backgroundColorPicker =  DKColorPickerWithKey(BG);

在使用时,上面的代码就相当于返回了一个在 NORMAL 时返回 #ffffffNIGHT 时返回 #343434 以及 RED 时返回 #fafafaDKColorPicker

pickerify

虽然说,我们使用色表以及 DKColorPicker 解决了,但是,到目前为止我们还没有解决第三方框架的问题。

比如我们使用了某个第三方框架,或者自己添加了某个 color 属性,比如说:

@interface DKView ()@property (nonatomic, strong) UIColor *weirdColor;@end

weirdColor 并没有对应的 DKColorPicker,但是,我们可以通过 pickerify 在想要使用 dk_weirdColorPicker 的地方生成这个对应的 picker:

@pickerify(DKView, weirdColor);

然后,我们就可以使用 dk_weirdColorPicker 属性了:

view.dk_weirdColorPicker = DKColorPickerWithKey(BG);

pickerify 其实是一个宏:

#define pickerify(KLASS, PROPERTY) interface \KLASS (Night) \@property (nonatomic, copy, setter = dk_set ## PROPERTY ## Picker:) DKColorPicker dk_ ## PROPERTY ## Picker; \@end \@interface \KLASS () \@property (nonatomic, strong) NSMutableDictionary<NSString *, DKColorPicker> *pickers; \@end \@implementation \KLASS (Night) \- (DKColorPicker)dk_ ## PROPERTY ## Picker { \return objc_getAssociatedObject(self, @selector(dk_ ## PROPERTY ## Picker)); \} \- (void)dk_set ## PROPERTY ## Picker:(DKColorPicker)picker { \objc_setAssociatedObject(self, @selector(dk_ ## PROPERTY ## Picker), picker, OBJC_ASSOCIATION_COPY_NONATOMIC); \[self setValue:picker(self.dk_manager.themeVersion) forKeyPath:@keypath(self, PROPERTY)];\[self.pickers setValue:[picker copy] forKey:_DKSetterWithPROPERTYerty(@#PROPERTY)]; \} \@end

这个宏根据传入的类和属性名,为我们生成了对应 picker 的存取方法,它也可以说是一种元编程的手段。

这里生成的 setter 方法不是标准意义上的驼峰命名法 dk_setweirdColorPicker:,因为我不知道怎么才能让大写首字母之后的属性添加到这里(如果各位读者有解决方案,欢迎提 PR 或者 issue)。

嵌入式 Ruby

由于框架中很多的代码,都是重复的,所以在这里使用了嵌入式 Ruby 模板来生成对应的文件 color.m.irb

//
//  <%= klass.name %>+Night.m
//  <%= klass.name %>+Night
//
//  Copyright (c) 2015 Draveness. All rights reserved.
//
//  These files are generated by ruby script, if you want to modify code
//  in this file, you are supposed to update the ruby code, run it and
//  test it. And finally open a pull request.#import "<%= klass.name %>+Night.h"
#import "DKNightVersionManager.h"
#import <objc/runtime.h>@interface <%= klass.name %> ()@property (nonatomic, strong) NSMutableDictionary<NSString *, DKColorPicker> *pickers;@end@implementation <%= klass.name %> (Night)<% klass.properties.each do |property| %><%= """
- (DKColorPicker)dk_#{property.name}Picker {return objc_getAssociatedObject(self, @selector(dk_#{property.name}Picker));
}- (void)dk_set#{property.cap_name}Picker:(DKColorPicker)picker {objc_setAssociatedObject(self, @selector(dk_#{property.name}Picker), picker, OBJC_ASSOCIATION_COPY_NONATOMIC);self.#{property.name} = picker(self.dk_manager.themeVersion);[self.pickers setValue:[picker copy] forKey:@\"#{property.setter}\"];
}
""" %><% end %>@end

这部分的实现并不在这篇文章的讨论范围之内,如果,对这部分看兴趣,可以看一下仓库中的 generator 文件夹,其中包含了代码生成器的全部代码。

小结

如果你对 DKNightVersion 的使用有兴趣,可以查看仓库的 README 文件,有人会说不要在项目中 ObjC runtime,我个人觉得是没有问题,AFNetworkingBlocksKit 也使用方法调剂来改变原有方法的实现,不能因为它强大就不使用它;正相反,有时候,使用 runtime 才能优雅地解决问题。

关注仓库,及时获得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github

成熟的夜间模式解决方案相关推荐

  1. win10夜间模式无效替代解决方案

    前话:win10夜间模式 上半年还是正常的,后面不知道啥原因不正常了,无法修改任何设置. 设置夜间模式各种参数后返回到设置主页面,再回去,参数又变成半年前设置的参数 了,也就是无法修改.因为半年前设置 ...

  2. Win10创意者更新刚发布 夜间模式就出BUG

    Win10创意者更新刚发布 夜间模式就出BUG 据外媒报道,微软刚刚才推出Windows 10 Creators Update更新没多久,就用用户爆出,升级更新后Windows10"夜间模式 ...

  3. 【android】夜间模式简单实现

    完整代码,请参考我的博客园客户端,git地址:http://git.oschina.net/yso/CNBlogs 关于阅读类的app,有个夜间模式真是太重要了. 那么有两种方式可以实现夜间模式 1: ...

  4. android官方夜间模式,Android夜间模式实践

    前言 由于项目需要,近段时间开发的夜间模式功能.主流的方案如下: 1.通过切换theme实现 2.通过resource id映射实现 3.通过Android Support Library的实现 方案 ...

  5. Android夜间模式实践

    前言 由于项目需要,近段时间开发的夜间模式功能.主流的方案如下: 1.通过切换theme实现 2.通过resource id映射实现 3.通过Android Support Library的实现 方案 ...

  6. linux nightshift调整,iOS9.3夜间模式怎么设置?iOS9.3 Night Shift设置教程

    iOS9.3正式版推送了,人们最感关注的或许就是它的夜间模式,Night Shift模式其实就是屏幕夜间色温自动调节功能,很多用户为了体验Night Shift这个功能,还特意下载了iOS9.3,那么 ...

  7. 安装向日葵远程后电脑亮度和夜间模式不能使用了——故障笔记

    最初: 我是真的手欠,我本以为是我乱搞显示器弄坏了电脑,导致电脑的亮度和夜间模式不能调节,后来发现原来是我不小心点了这个东西,呜呜呜~ 问题: 根据大神的讲解,罪魁祸首竟然是:远程控制软件向日葵. 可 ...

  8. android自动夜间模式吗,Android夜间模式的实现方案

    原标题:Android夜间模式的实现方案 作者简介 本篇来自 Sunlight1024的投稿,详细地讲解了关于Android应用的夜间模式的实现,希望大家喜欢! Sunlight1024的博客地址: ...

  9. android studio夜间模式,android studio怎样实现夜间模式

    满意答案 noxlqub 2016.05.01 采纳率:51%    等级:7 已帮助:210人 关于阅读类的app,有个夜间模式真是太重要了. 那么有两种方式可以实现夜间模式 1:修改theme,重 ...

最新文章

  1. Android实时获取音量(单位:分贝)
  2. android native堆内存泄露,Android Native内存泄露检测
  3. [恢]hdu 2098
  4. 强碱性食品 高嘌呤食物
  5. 命令行下安装的tensorflow怎么打开_CourseMaker微课制作教程18:录ppt一直“正在打开……”及WPS下ppt满屏放映怎么办?...
  6. Canvas 画占比图 解决canvas锯齿 bug
  7. html5与之前版本,IT兄弟连HTML5教程HTML5做到了与之前版本的兼容
  8. 机器学习教程 之 Boosting 与 bagging:集成学习框架
  9. [转] 使用CPN Tools工具做简单的登录模型()
  10. switch语句查询水果价格
  11. 关于结构方程模型SEM评价指标
  12. Visual Studio更改程序图标傻瓜式教程
  13. 计算机编程 计算存款利息,作业报告12 定期存款利息计算器
  14. qt无法显示图片的原因
  15. 途牛2021年第三季度营收约1亿元,同比、环比均有所下降
  16. 使用Travis-CI自动部署博客
  17. 职场是个技术活-马未都
  18. 计算机辅助教学是人工智能应用,人工智能教学论文,关于人工智能存计算机辅助教学中应用相关参考文献资料-免费论文范文...
  19. 计算机中国教育部排名,2019全国高校排行榜_全国高校专业排名哪家强 教育部发布权威榜单...
  20. oppo手机出现android什么坏了,oppo开不了机怎么办 详细介绍【图解】

热门文章

  1. HTML打开服务器的exe,打开远程服务器的html
  2. java字符串替换标点符号_替换/忽略字符串java中的标点符号
  3. Shorten command line 解决方案
  4. python写货币转换_如何在Python中将货币字符串转换为浮点数?
  5. 自动定位失败_端到端定位5G SA接入问题
  6. 天翼云从业认证课后习题(3.1天翼云计算产品)
  7. 在Opendaylight中karaf启动的时候自动安装自己编写的feature
  8. java 枚举类 enum
  9. java实现大整数的加减乘除法(百练OJ:2736、2737、2980、2981)
  10. 【信息化】CIO议题营销模型