日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

成熟的夜间模式解决方案

發(fā)布時(shí)間:2025/3/19 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 成熟的夜间模式解决方案 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

關(guān)注倉庫,及時(shí)獲得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github

從開始寫 DKNightVersion 這個(gè)框架到現(xiàn)在已經(jīng)將近一年了,目前整個(gè)框架的設(shè)計(jì)也趨于穩(wěn)定。

其實(shí)夜間模式的實(shí)現(xiàn)就是相當(dāng)于多主題加顏色管理。而最新版本的 DKNightVersion 已經(jīng)很好的解決了這個(gè)問題。

在正式介紹目前版本的實(shí)現(xiàn)之前,我會先簡單介紹一下 1.0 時(shí)代的 DKNightVersion 的實(shí)現(xiàn),為各位讀者帶來一些新的思路,也確實(shí)想梳理一下這個(gè)框架是如何演變的。

我們會以對 backgroundColor 為例說明整個(gè)框架的工作原理。

方法調(diào)劑的版本

如何在不改變原有的架構(gòu),甚至不改變原有的代碼的基礎(chǔ)上,為應(yīng)用優(yōu)雅地添加夜間模式成為很多開發(fā)者不得不面對的問題。這也是 1.0 時(shí)代的 DKNightVersion 想要實(shí)現(xiàn)的目標(biāo)。

其核心思路就是使用方法調(diào)劑修改 backgroundColor 的存取方法

使用 nightBackgroundColor

在思考之后,我想到,想要在不改動原有代碼的基礎(chǔ)上實(shí)現(xiàn)夜間模式只能通過在分類中添加 nightBackgroundColor 屬性,并且使用方法調(diào)劑改變 backgroundColor 的 setter 方法。

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

在當(dāng)前主題為 DKThemeVersionNormal 時(shí),將顏色保存至 normalBackgroundColor 中,然后再調(diào)用原 backgroundColor 的 setter 方法,更新視圖的顏色。

DKNightVersionManager

這里只解決了顏色設(shè)置的問題,下面會說明,如果在主題改變時(shí),實(shí)時(shí)更新顏色,而不用重新進(jìn)入當(dāng)前頁面。

整個(gè) DKNightVersion 都是由一個(gè) DKNightVersionManager 的單例來管理的,而它的主要工作就是負(fù)責(zé)改變應(yīng)用的主題、并在主題改變時(shí)通知其它視圖更新顏色

- (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];}}}} }

如果主題更新,那么就會遞歸地調(diào)用 changeColor 方法,刷新全部的視圖顏色,而這個(gè)方法的實(shí)現(xiàn)比較簡單:

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

上面就是整個(gè)框架在 1.0 版本時(shí)的實(shí)現(xiàn)思路。不過這個(gè)版本的 DKNightVersion 在實(shí)際應(yīng)用中會有比較多的問題:

  • 在高速滾動的 scrollView 上面來回切換夜間模式,會出現(xiàn)顏色錯(cuò)亂的問題

  • 由于對 backgroundColor 屬性進(jìn)行不合適的方法調(diào)劑,其行為無法預(yù)測,比如:在設(shè)置顏色后,再取出,不一定與設(shè)置時(shí)傳入的顏色相同

  • 無法適配第三方 UI 控件

  • 使用色表的版本

    為了解決 1.0 中的各種問題,我決定在 2.0 版本中放棄對 nightBackgroundColor 的使用,并且重新設(shè)計(jì)底層的實(shí)現(xiàn),轉(zhuǎn)而使用更為穩(wěn)定安全的方法實(shí)現(xiàn)夜間模式,先看一下效果圖:

    新的實(shí)現(xiàn)不僅能夠支持夜間模式,而且能夠支持多主題。

    DKColorPicker

    與上一個(gè)版本實(shí)現(xiàn)上的不同,在 2.0 中刪除了全部的 nightBackgroundColor,使用一個(gè)名為 dk_backgroundColorPicker 的屬性取代它。

    @property (nonatomic, copy) DKColorPicker dk_backgroundColorPicker;

    這個(gè)屬性其實(shí)就是一個(gè) block,它接收參數(shù) DKThemeVersion *themeVersion,但是會返回一個(gè) UIColor *:

    在第一次傳入 picker 或者每次主題改變時(shí),都會將當(dāng)前主題 DKThemeVersion 傳入 picker 并執(zhí)行,然后,將得到的 UIColor 賦值給對應(yīng)的屬性 backgroundColor 更新視圖顏色。

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

    比如下面使用 DKColorPickerWithRGB 創(chuàng)建一個(gè)臨時(shí)的 DKColorPicker:

  • 在 DKThemeVersionNormal 時(shí)返回 0xffffff

  • 在 DKThemeVersionNight 時(shí)返回 0x343434

  • 在自定義的主題下返回 0xfafafa (這里的順序與色表中主題的順序有關(guān))

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

    同時(shí),每一個(gè)對象還持有一個(gè) pickers 數(shù)組,來存儲自己的全部 DKColorPicker:

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

    在第一次使用這個(gè)屬性時(shí),當(dāng)前對象注冊為 DKNightVersionThemeChangingNotificaiton 通知的觀察者。

    在每次收到通知時(shí),都會調(diào)用 night_update 方法,將當(dāng)前主題傳入 DKColorPicker,并再次執(zhí)行,并將結(jié)果傳入對應(yīng)的屬性 [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}];}]; }

    也就是說,在每次改變主題的時(shí)候,都會發(fā)出通知。

    DKColorTable

    雖然我們在上面臨時(shí)創(chuàng)建了一些 DKColorPicker。不過在 DKNightVersion 中,我更推薦使用色表,來減少相同的 DKColorPicker 的創(chuàng)建,并且能夠更好地管理整個(gè)應(yīng)用中的顏色:

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

    上面就是默認(rèn)色表文件 DKColorTable.txt 中的內(nèi)容,其中,第一行表示主題,NORMAL 主題必須存在,而且必須為第一列,而最右面的 BG、SEP 就是對應(yīng) DKColorPicker 的 key。

    self.tableView.dk_backgroundColorPicker = DKColorPickerWithKey(BG);

    在使用時(shí),上面的代碼就相當(dāng)于返回了一個(gè)在 NORMAL 時(shí)返回 #ffffff、NIGHT 時(shí)返回 #343434 以及 RED 時(shí)返回 #fafafa 的 DKColorPicker。

    pickerify

    雖然說,我們使用色表以及 DKColorPicker 解決了,但是,到目前為止我們還沒有解決第三方框架的問題。

    比如我們使用了某個(gè)第三方框架,或者自己添加了某個(gè) color 屬性,比如說:

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

    weirdColor 并沒有對應(yīng)的 DKColorPicker,但是,我們可以通過 pickerify 在想要使用 dk_weirdColorPicker 的地方生成這個(gè)對應(yīng)的 picker:

    @pickerify(DKView, weirdColor);

    然后,我們就可以使用 dk_weirdColorPicker 屬性了:

    view.dk_weirdColorPicker = DKColorPickerWithKey(BG);

    pickerify 其實(shí)是一個(gè)宏:

    #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

    這個(gè)宏根據(jù)傳入的類和屬性名,為我們生成了對應(yīng) picker 的存取方法,它也可以說是一種元編程的手段。

    這里生成的 setter 方法不是標(biāo)準(zhǔn)意義上的駝峰命名法 dk_setweirdColorPicker:,因?yàn)槲也恢涝趺床拍茏尨髮懯鬃帜钢蟮膶傩蕴砑拥竭@里(如果各位讀者有解決方案,歡迎提 PR 或者 issue)。

    嵌入式 Ruby

    由于框架中很多的代碼,都是重復(fù)的,所以在這里使用了嵌入式 Ruby 模板來生成對應(yīng)的文件 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

    這部分的實(shí)現(xiàn)并不在這篇文章的討論范圍之內(nèi),如果,對這部分看興趣,可以看一下倉庫中的 generator 文件夾,其中包含了代碼生成器的全部代碼。

    小結(jié)

    如果你對 DKNightVersion 的使用有興趣,可以查看倉庫的 README 文件,有人會說不要在項(xiàng)目中 ObjC runtime,我個(gè)人覺得是沒有問題,AFNetworking、 BlocksKit 也使用方法調(diào)劑來改變原有方法的實(shí)現(xiàn),不能因?yàn)樗鼜?qiáng)大就不使用它;正相反,有時(shí)候,使用 runtime 才能優(yōu)雅地解決問題。

    關(guān)注倉庫,及時(shí)獲得更新:iOS-Source-Code-Analyze
    Follow: Draveness · Github

    總結(jié)

    以上是生活随笔為你收集整理的成熟的夜间模式解决方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。