最近公司業(yè)務(wù)需求要更換APP主題。最開始是一個(gè)地方一個(gè)地方去改氧映,而且項(xiàng)目中很多老代碼是用xib寫的窄俏,習(xí)慣純代碼編程的我改的很難受。而且以后指不定要再次更改主題城须。
于是我定義了幾個(gè)主要顏色的宏,代碼中只要是設(shè)置顏色的地方就用宏米苹。這樣只需要改一次糕伐,當(dāng)要切換主題的時(shí)候直接對(duì)宏進(jìn)行更改就行了。
結(jié)合已做好的切換主題功能蘸嘶,再加上一個(gè)暗黑模式判斷良瞧,如果當(dāng)前是暗黑模式就用A套色值陪汽,如果不是就用B套色值,這樣就實(shí)現(xiàn)了暗黑模式的適配了褥蚯。
一挚冤、定義的宏:
代碼中只要是設(shè)置顏色的地方就用定義好的顏色。(下面?zhèn)€別宏只是我的項(xiàng)目場(chǎng)景中會(huì)使用到的赞庶,并不適用于所有APP训挡,可自行針對(duì)自己的項(xiàng)目定義。有些顏色兩種模式下沒有變動(dòng))
/// 暗黑模式 YES是
#define CKDarkMode @available(iOS 13.0, *) && UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark
// MARK: - 十六進(jìn)制顏色
#define HexOf(rgbValue) Hex_A(rgbValue,1.0)
#define Hex_A(rgbValue,a) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:a]
// MARK: - 用全局變量設(shè)置背景歧强、文字,可以優(yōu)雅的主題切換 (取全局唯一性的名稱,便于維護(hù);最前面的優(yōu)先級(jí)最高)
#define Color_Bg? ? ? ? CKDarkMode?HexOf(0x191C32):HexOf(0xf4f4f4) //背景主題顏色? ? 黑色/白色
#define Color_ContView? CKDarkMode?HexOf(0x1D213B):HexOf(0xffffff) //內(nèi)容澜薄、cell顏色? 深藍(lán)色/白色 如果背景和cell顏色一樣,就都用這個(gè)
#define Color_Title? ? CKDarkMode?HexOf(0xFFFFFF):HexOf(0x393939) //主文字顏色? ? ? 白色/黑色
#define Color_Subtitle? CKDarkMode?HexOf(0x999999):HexOf(0x999999) //副文字顏色? ? ? 淺白色/灰色
#define Color_Green? ? CKDarkMode?HexOf(0x45C98F):HexOf(0x45C98F) //綠漲? ? ? ? ? (行情、交易)
#define Color_Red? ? ? CKDarkMode?HexOf(0xEF0C47):HexOf(0xEF0C47) //紅跌? ? ? ? ? (行情摊册、交易)
//
#define Color_NavBg? ? CKDarkMode?HexOf(0x1D213B):HexOf(0xffffff) //導(dǎo)航欄背景顏色
#define Color_NavTitle? CKDarkMode?HexOf(0xFFFFFF):HexOf(0x393939) //導(dǎo)航欄標(biāo)題顏色
#define Color_TabbarBg? CKDarkMode?HexOf(0x1D213B):HexOf(0xffffff) //標(biāo)簽欄背景顏色
#define Color_Selected? CKDarkMode?HexOf(0x46CA8F):HexOf(0x46CA8F) //綠色 (按鈕選中肤京、已認(rèn)證狀態(tài)的顏色)
#define Color_Line? ? ? CKDarkMode?HexOf(0x191C32):HexOf(0xf4f4f4) //分割線
#define Color_DarkGray? CKDarkMode?HexOf(0x333333):HexOf(0x333333) //深灰色
#define Color_Gray? ? ? CKDarkMode?HexOf(0x666666):HexOf(0x666666) //灰色
#define Color_LightGray CKDarkMode?HexOf(0x999999):HexOf(0x999999) //淺灰色
#define Color_InputBg? CKDarkMode?HexOf(0x191C32):HexOf(0xf4f4f4) //輸入框背景顏色
#define Color_DarkBlue? CKDarkMode?HexOf(0x191C32):HexOf(0x191C32) //深藍(lán)色 (特殊顏色)
#define Color_HalfTitle CKDarkMode?Hex_A(0x999999, 0.5):Hex_A(0x999999, 0.5);//半透明文字 色值是副標(biāo)題的一半
如果想關(guān)閉暗黑模式,直接設(shè)置:
#define CKDarkMode NO
二茅特、遇到的問題:
1忘分、最開始我是用的self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark去做判斷,但是有些類并沒有UITraitCollection這個(gè)屬性白修,很多地方報(bào)錯(cuò)妒峦。
解決方案:
改用UITraitCollection.current屬性來獲取當(dāng)前App的顏色模式。
2熬荆、CGColorRef相關(guān):
bt.layer.borderColor = Color_Selected.CGColor;
報(bào)錯(cuò):
Incompatible operand types ('UIColor * _Nonnull' and 'CGColorRef _Nonnull' (aka 'struct CGColor *'))
解決方案:
UIColor *color = Color_Selected;
bt.layer.borderColor = color.CGColor;
3.每次打開APP都能展示正常的模式舟山;但是如果打開APP后再切換模式,已經(jīng)加載出來的頁面依然會(huì)顯示切換之前的主題模式卤恳。
解決方案:
在頁面中添加通知累盗,獲取到切換主題的通知后重新刷新一下頁面顏色(類似于項(xiàng)目中的國(guó)際化通知處理邏輯)
4.項(xiàng)目中個(gè)別頁面的狀態(tài)欄是固定的白色,在切換頁面的時(shí)候會(huì)把狀態(tài)欄切換回主題顏色黑色突琳,但是在暗黑模式下就會(huì)有問題若债,因?yàn)榘岛谀J较抡麄€(gè)APP的狀態(tài)欄都是白色的,這時(shí)不需要切換回黑色拆融。
解決方案:
添加一個(gè)UIStatusBarStyle變量記錄主題狀態(tài)欄顏色蠢琳,這樣可以不用在控制器內(nèi)做太多額外的判斷。如果用 @available(iOS 13.0, *) 去做判斷镜豹,需求變更后還要每個(gè)地方都去改動(dòng)代碼傲须。用了這種方式,后面即使更改了主題或者關(guān)閉了暗黑模式趟脂,也不用一一去改代碼泰讽;也可以通過上面定義的宏CKDarkMode做判斷,關(guān)閉暗黑模式時(shí)只需把CKDarkMode設(shè)置為NO就行
UIStatusBarStyle _themeStatusBarStyle;//記錄主題狀態(tài)欄顏色
- (void)viewWillAppear:(BOOL)animated {
? ? [super viewWillAppear:animated];
? ? _themeStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
? ? // 設(shè)置狀態(tài)欄顏色為白色
? ? [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
}
- (void)viewWillDisappear:(BOOL)animated{
? ? [super viewWillDisappear:animated];
? ? // 恢復(fù)狀態(tài)欄顏色為主題顏色
? ? [UIApplication sharedApplication].statusBarStyle = _themeStatusBarStyle;
}
5、使用了宏的地方都會(huì)報(bào)警告已卸,提示我要做系統(tǒng)版本判斷佛玄,但是實(shí)際上我已經(jīng)在CKDarkMode中判斷過了,系統(tǒng)檢測(cè)不到:
'currentTraitCollection' is only available on iOS 13.0 or newer
解決方案:使用UIColor擴(kuò)展累澡。
999+的警告有點(diǎn)影響代碼視覺體驗(yàn)梦抢,后面應(yīng)該會(huì)改用擴(kuò)展的方式。如果有更好的解決方案請(qǐng)?jiān)谙路搅粞浴?/p>
三愧哟、UITraitCollection介紹:
1奥吩、在 iOS 13 中,我們可以通過 UITraitCollection 來判斷當(dāng)前系統(tǒng)的模式翅雏。UIView 和 UIViewController 圈驼、UIScreen、UIWindow都已經(jīng)遵從了UITraitEnvironment這個(gè)協(xié)議望几,因此這些類都擁有一個(gè)叫做?traitCollection的屬性绩脆,在這些類中,我們可以這樣去判斷當(dāng)前 App 的顏色模式:
BOOL isDark = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark);
2橄抹、另外靴迫,我們還可以通過?UITraitCollection.current這個(gè)屬性來獲取當(dāng)前 App 的顏色模式。
3楼誓、如果暫時(shí)不想開放這個(gè)功能玉锌,可以先暫時(shí)全局關(guān)閉暗黑模式:
在 Info.plist 文件中,添加 key 為 User Interface Style疟羹,類型為 String主守,value 設(shè)置為?Light (Dark)即可,如果重新打開就把這條設(shè)置刪除榄融。(這種方式是在APP整個(gè)生命周期內(nèi)關(guān)閉了暗黑模式参淫;上面的設(shè)置#define CKDarkMode NO只是用代碼做了判斷并只在用了宏的地方起作用)。
4愧杯、在 iOS 13中涎才,UIView、UIViewController 力九、UIWindow有了一個(gè)?overrideUserInterfaceStyle的新屬性耍铜,可以覆蓋系統(tǒng)的模式。
單個(gè)頁面或視圖關(guān)閉暗黑模式跌前,設(shè)置?overrideUserInterfaceStyle為對(duì)應(yīng)的模式棕兼,強(qiáng)制限制該視圖與其子視圖以設(shè)置的模式進(jìn)行展示,不跟隨系統(tǒng)模式改變進(jìn)行改變抵乓。
self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
設(shè)置此屬性會(huì)影響當(dāng)前view / viewController / window以及它下面的任何內(nèi)容程储。
如果你希望一個(gè)子視圖監(jiān)聽系統(tǒng)的模式蹭沛,請(qǐng)將?overrideUserInterfaceStyle屬性設(shè)置為.unspecified臂寝。
四章鲤、拓展
除了我的這種實(shí)現(xiàn)方案,還有其他方案可以適配暗黑模式:
1咆贬、UIColor擴(kuò)展:
+(UIColor *)generateDynamicColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor{
????if(@available(iOS 13.0, *)) {
? ? ? ? UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
????????????if(traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {
????????????????return lightColor;
? ? ? ? ? ? }else{
????????????????return darkColor;
? ? ? ?}}];
????????return dyColor;
? ? }else{
????????return lightColor;
}}
問題:
這種寫法要在每個(gè)使用的地方分別傳一個(gè)普通模式的顏色和暗黑模式的顏色败徊,不方便維護(hù)。
解決方案:
可以定義幾個(gè)常用顏色函數(shù)掏缎,特殊的場(chǎng)景就用上面的方法皱蹦,這樣就不需要在每個(gè)地方都控制顏色值了。
+(UIColor *)ContViewColor{
????if(@available(iOS 13.0, *)) {
? ? ? ? UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
????????????if(traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
????????????????return ColorA;? ? ? ? ? ?
????????????}else{
????????????????return ColorB; ? ? ? ? ??
???????}?}];
????????return dyColor;? ?
????}else{
????????return ColorB; ??
}}
2眷蜈、可以在?Images.xcassets中定義幾種常用顏色沪哺,并為顏色再配置一個(gè)用于暗黑模式的對(duì)應(yīng)的顏色:
3、在?Images.xcassets中配置不同模式下的圖片酌儒,當(dāng)你設(shè)置為暗黑模式后就會(huì)自動(dòng)顯示對(duì)應(yīng)的圖片
4辜妓、啟動(dòng)圖:
LaunchScreen.storyboard可以像普通的圖片那樣針對(duì)深色模式設(shè)置另外的一張圖片
5、layer:
UIColor *resolvedColor = [[UIColor labelColor] resolvedColorWithTraitCollection:self.view.traitCollection];
label.layer.borderColor = resolvedColor.CGColor;
6忌怎、UIActivityIndicatorView 的 style:
iOS 13前 的 UIActivityIndicatorViewStyle:
typedef NS_ENUM(NSInteger, UIActivityIndicatorViewStyle) {?
? ? UIActivityIndicatorViewStyleWhiteLarge,
? ? UIActivityIndicatorViewStyleWhite,
? ? UIActivityIndicatorViewStyleGray,
};
iOS 13后籍滴,由于暗黑模式,上述三個(gè)屬性都被廢棄榴啸,建議使用如下 style:
typedef NS_ENUM(NSInteger, UIActivityIndicatorViewStyle) {
????UIActivityIndicatorViewStyleMedium,
????UIActivityIndicatorViewStyleLarge,
????UIActivityIndicatorViewStyleWhiteLarge API_DEPRECATED_WITH_REPLACEMENT("UIActivityIndicatorViewStyleLarge",
????UIActivityIndicatorViewStyleWhite API_DEPRECATED_WITH_REPLACEMENT("UIActivityIndicatorViewStyleMedium",
????UIActivityIndicatorViewStyleGray API_DEPRECATED_WITH_REPLACEMENT("UIActivityIndicatorViewStyleMedium",
};
7孽惰、Status Bar 的 style :
在 iOS 13 之前,狀態(tài)欄的樣式的枚舉值也帶有著明顯的顏色傾向鸥印,UIStatusBarStyleDefault勋功、UIStatusBarStyleLightContent。
現(xiàn)在库说,狀態(tài)欄的 default 樣式會(huì)根據(jù)當(dāng)前的模式展示不同的顏色狂鞋,而原有的?lightContent樣式則新增一個(gè)?darkContent的樣式與之對(duì)應(yīng)。
typedef NS_ENUM(NSInteger, UIStatusBarStyle) {
? ? UIStatusBarStyleDefault? ? ? = 0, // Automatically chooses light or dark content based on the user interface style
? ? UIStatusBarStyleLightContent = 1, // Light content,foruse on dark backgrounds? ?
????UIStatusBarStyleDarkContent? = 3, // Dark content,foruse on light backgrounds
};
8璃弄、SF Symbols
注意:
命名時(shí)要保證這個(gè)名字的全局唯一性要销,避免和項(xiàng)目中其他命名雷同,這樣可以保證全局搜索時(shí)搜索到的結(jié)果只有你想搜索的內(nèi)容夏块,便于維護(hù)疏咐。例如你取名RedColor,會(huì)搜索到很多其他沒用的信息脐供。這種命名思路也可以用在其他地方浑塞。
除了改背景顏色、文字顏色政己,還需要替換圖標(biāo)酌壕、圖片,這個(gè)需要UI配合切圖。
參考: