使用DKNightVersion实现夜间模式

作者: HiIgor | 来源:发表于2016-08-01 15:53 被阅读2783次

概述

DKNightVersion是github上面一个用于实现iOS应用夜间模式和多种主题的开源库。github上面有两个star数较高的库,DKNightVersion和SwiftTheme。后者源码是用swift实现的,OC和Swift混编导致应用的体积大幅度增加,于是选择了DKNightVersion。

使用方法

举例说明,此处假设我们的Theme只有两种:模式,夜间模式。

DKColorPicker Examples
view.dk_backgroundColorPicker = DKColorPickerWithColors([UIColor whiteColor], [UIColor darkGrayColor]); // => view的backgroundColor在日间模式、夜间模式下分别为white、darkGray。
label.dk_textColorPicker = DKColorPickerWithColors([UIColor whiteColor], [UIColor darkGrayColor]);      // => label的textColor在日间模式、夜间模式下分别为white、darkGray。
tabBar.dk_barTintColorPicker = DKColorPickerWithColors([UIColor whiteColor], [UIColor darkGrayColor]);  // => tabBar的barTintColor在日间模式、夜间模式下分别为white、darkGray。

等等,能用别的方式来创建dk_XXXColorPicker吗,比如RGB数值?当然可以DKNightVersion提供了 DKColorPickerWithRGB(NSUInteger normal, ...)接口来根据色号生成dk_XXXColorPicker。以上我们的Theme有n种,那么我们就需要在在dk_XXXColorPicker里面传入n个代表颜色的参数。
也可以设置不同Theme下的图片。

DKImagePicker Examples
imageView.dk_image = DKImagePickerWithImages([UIImage imageNamed:@"white"], [UIImage imageNamed:@"black"]); // => imageView的image在日间模式、夜间模式分别为图片名为white、black代表的图片。

也有很多方法来生成dk_image,例如:DKImagePickersWithImageNames(@"white",@"black"),直接根据图片名来生成dk_image,等等。

设置好了不同Theme下的颜色和图片,如下代码即可:

Theme Switch
[DKNightVersionManager sharedManager].themeVersion = DKThemeVersionNormal;// or DKThemeVersionNight => 将当前的主题切换到日间模式或夜间模式。

实现思路

先看下上面的例子中用到的一些属性。

UIView+night
// DKColorPicker definition
@property (nonatomic, copy, setter = dk_setBackgroundColorPicker:) DKColorPicker dk_backgroundColorPicker;
// DKImagePicker definition
@property (nonatomic, copy, setter = dk_setTintColorPicker:) DKColorPicker dk_tintColorPicker;
UIImageView+night
@property (nullable, nonatomic, copy, setter = dk_setImagePicker:) DKImagePicker dk_imagePicker;

从使用方法里面可以看到我们在设置给UI控件的DKColorPicker属性赋值时,传入了n个颜色。n个颜色对应了n中Theme,而且他们根据索引一一对应。

DKNightVersionManager是一个用于管理主题的单例。当[DKNightVersionManager sharedManager].themeVersion 发生改变时,也就是当前的Theme发生了个改变。会发一个通知告诉所有的设置过DKColorPicker的UI控件。
UI控件收到通知后去找DKNightVersionManager去拿到当前的Theme,根据Theme更新UI控件的相关属性(backgroundColor,tintColor, textColor, image等),这边是实现这个功能一个大体的思路。

DKColorPicker是什么?它并不是一个用来存color的数组,它的定义是这样的:

DKColorPicker,DKImagePicker
typedef UIColor *(^DKColorPicker)(DKThemeVersion *themeVersion); 
typedef UIImage *(^DKImagePicker)(DKThemeVersion *themeVersion);

它是一个block,传入一个我们已经定义好了的Theme,这个block给出一个color,用以更新。DKImagePicker同理。
每个对象都有一个pickers属性

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

在第一次使用这个属性时,当前对象注册为 DKNightVersionThemeChangingNotification 通知的观察者。pickers属性只有在对象的某个DKColorPicker/DKImagePicker首次被赋值时才会被创建。

dk_backgroundColorPicker setter
- (void)dk_setBackgroundColorPicker:(DKColorPicker)picker {
    objc_setAssociatedObject(self, @selector(dk_backgroundColorPicker), picker, OBJC_ASSOCIATION_COPY_NONATOMIC);
    self.backgroundColor = picker(self.dk_manager.themeVersion);
    [self.pickers setValue:[picker copy] forKey:@"setBackgroundColor:"];
}

当Theme发生变化时,DKNightVersionManager会发出通知,所有监听DKNightVersionThemeChangingNotification的对象调用night_update方法去更新色值和图片。实现如下:

notification action
- (void)night_updateColor {
    [self.pickers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull selector, DKColorPicker  _Nonnull picker, BOOL * _Nonnull stop) {
        SEL sel = NSSelectorFromString(selector);
        // picker根据Theme拿到color/image值
        id result = picker(self.dk_manager.themeVersion);
        [UIView animateWithDuration:DKNightVersionAnimationDuration
                         animations:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                             [self performSelector:sel withObject:result];
#pragma clang diagnostic pop
                         }];
    }];
}

从dk_backgroundColor的setter方法中可以知道,上面的selector一般为setBackgroundColor:,setTintColor;,setImage:,根据selector生成方法,然后去更新对象的颜色,图片等。这个库已经包含了所有的原生UI控件的color和image属性,通过runtime,category给UI控件添加属性。

作者推荐我们使用如下的方式来创建DKColorPicker。在DKColorTable.txt中,配置我们需要的色值和主题,内容如下:

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

NORMAL 、NIGHT、RED分别对应三个主题。
那么通过 DKColorPickerWithKey(BG),生成对应三个主题的DKColoPicker,并且目前的Theme只能通过修改DKColorTable.txt的文件内容进行管理。

总结

  • 这个库可以实现我们当前的大多数的需求,目前这个库还不能比较方便的解决富文本的不同主题的不同样式问题,我们可以参照它的实现给需要使用富文本的控件添加DKNightVersionThemeChangingNotification监听,从而根据不同的Theme做出不同的展现,这个思路当然也可以拓展到其他地方,虽然会造成比较强的耦合关系,如不同主题下的不同样式的展现等等。
  • 鉴于当前Theme的管理方式,而且DKColorPicker用DKColorPickerWithKey()以外的其他方法创建传入的数值并非动态的,所以以后增加Theme时可能会比较棘手。作者不推荐我们手动创建DKColorPicker,而推荐使用DKColorTable.txt 来进行主题管理, 这样DKColorPicker中包含的色值由DKColorTable.txt中的配置决定,这样会更加方便。
  • 它不仅仅支持原生的backgroundColor,tintColor等属性,可以给自定义的控件添加一个你想要的的color/image属性,例如pressedColor,在不同的主题做出不同的展现。
  • 这个库的一个比较好的实现我觉得是把Block当做一个属性赋值给对象而不是存储一个数组或字典,然后根据其他变量的变化做出响应的一个思路。
  • 使用的时候我们自己新建一个theme_color_table.txt文件,然后在appdelegate调用[[DKColorTable sharedColorTable] setFile:@"theme_color_table.txt"]; 注意如果使用系统自带的编辑软件编辑theme_color_table.txt 的话,可能会出现空格导致崩溃而且一般看不出来,最好用专用的文字编辑软件去添加新的配置。

相关文章

网友评论

  • 景彧:老兄,你看下这个问题要怎么解决啊:
    2018-01-03 17:47:11.947524+0800 Example[55136:1551106] DKColorTable:
    NORMAL NIGHT RED
    #ffffff #343434 #fafafa BG
    #aaaaaa #313131 #aaaaaa SEP
    #0000ff #ffffff #fa0000 TINT
    #000000 #ffffff #000000 TEXT
    #ffffff #444444 #ffffff BAR
    #f0f0f0 #222222 #dedede HIGHLIGHTED
    2018-01-03 17:47:11.947925+0800 Example[55136:1551106] -[UIBarButtonItem dk_setTintColorPicker:]: unrecognized selector sent to instance 0x7fe7a5518c40

    你代码库里边的方法,Xcode不识别了😣
  • 咸鱼攻城狮: UIImage *navBackImage = [DKImage getImageNameWithByPath:@"Nav/Nav_Back"];
    navBackImage = [navBackImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    [[UINavigationBar appearance] setBackIndicatorImage:navBackImage];
    [[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:navBackImage];
    我在iOS11的NavigationController的基类里这么自定义返回按钮,而且在皮肤切换时候也调用了这个方法,但是无法执行皮肤的切换。有什么方法解决吗?
  • 扑腾的蛾子:您好,self.menuBtn.imageView.dk_imagePicker = DKImagePickerWithNames,按钮这样设置图片,但是选中后就会变回原先设置的图片了,该怎么办
    扑腾的蛾子:@断肠人在摸虾 谢谢,明白了
    扑腾的蛾子:@断肠人在摸虾 我试试
    HiIgor:api不是这样用的,看下UIButton+Night.h文件里面的用法
  • 大学霸:在吗,我用了这个手机系统升级后输入切换输入法就会崩溃。请问会有这个原因吗
    HiIgor:@小奈同学 打全局断点调吧。
    大学霸:@断肠人在摸虾 断在主函数,没用之前是没问题的,用了之后就只能使用系统输入法,切输入法就崩溃
    HiIgor:应该没有关系吧,调试断哪儿呢?
  • cl9000:如果一个项目已经完成了,再加这个框架会不会很麻烦
    HiIgor:@cl9000 技术上没有什么难点,主要是时间比较久。
  • FR_Zhang:比如我想在正常模式下,显示某个view为clear的颜色,夜间模式下显示black,alpha=0.25的颜色该怎么设置
    HiIgor:@FR_Zhang 嗯 如果觉得监听太麻烦,这样做也可以。
    FR_Zhang:@断肠人在摸虾 说实话 没办法用,想了几种方案也不行,但是存在绕路方案......生成两张图片然后设置dk_imagePicker
    HiIgor:@FR_Zhang 这样的话没法用picker,只能用监听主题改变之后来根据当前主题进行设置
  • FR_Zhang:swift中如何调用DKColorPickerWithColors方法 ,求指导
    HiIgor:oc和swift之间桥结的文件里面引入这个库的头文件就能用了吧。
  • 杰铭的博客:楼主,我自定义tabbar,要重启app才会有效果哎,是怎么回事呢
    大学霸:tabbar图片怎么切换呢。
    杰铭的博客:@断肠人在摸虾 已经解决了,不知道为什么tabbar的effectView没有转颜色,我单独把它拎出来把颜色换了
    HiIgor:贴下代码看看
  • 携一两本单色书来:因为要修改夜间模式下的label字体颜色,所以就在DKColorTable.txt中试着修改了text,night的颜色,结果改完就报错了,改回去也没有用。最后重新pod才弄好,大哥,怎么修改label夜间下的字体颜色呢?我现在用的是 self.titleLabel.dk_textColorPicker = DKColorPickerWithKey(TEXT);
    携一两本单色书来:又试了一次,还是报错, reason: 'Invalid parameter not satisfying: themes.count == colors.count'。
    HiIgor:@携一两本单色书来 是不是DKColorTable.txt里面多了空行

本文标题:使用DKNightVersion实现夜间模式

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