iOS13 (五)暗黑模式Dark Mode

最近公司業務需求要更換APP主題。最開始是一個地方一個地方去改,並且項目中不少老代碼是用xib寫的,習慣純代碼編程的我改的很難受。並且之後指不定要再次更改主題。html

因而我定義了幾個主要顏色的宏,代碼中只要是設置顏色的地方就用宏。這樣只須要改一次,當要切換主題的時候直接對宏進行更改就好了。ios

結合已作好的切換主題功能,再加上一個暗黑模式判斷,若是當前是暗黑模式就用A套色值,若是不是就用B套色值,這樣就實現了暗黑模式的適配了。編程

.bash

1、定義的宏:

代碼中只要是設置顏色的地方就用定義好的顏色。(下面個別宏只是個人項目場景中會使用到的,並不適用於全部APP,可自行鍼對本身的項目定義。有些顏色兩種模式下沒有變更)app

/// 暗黑模式 YES是
#define CKDarkMode @available(iOS 13.0, *) && UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark
// MARK: - 十六進制顏色
#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: - 用全局變量設置背景、文字,能夠優雅的主題切換 (取全局惟一性的名稱,便於維護;最前面的優先級最高)
#define Color_Bg CKDarkMode?HexOf(0x191C32):HexOf(0xf4f4f4) //背景主題顏色 黑色/白色
#define Color_ContView CKDarkMode?HexOf(0x1D213B):HexOf(0xffffff) //內容、cell顏色 深藍色/白色 若是背景和cell顏色同樣,就都用這個
#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) //導航欄背景顏色
#define Color_NavTitle CKDarkMode?HexOf(0xFFFFFF):HexOf(0x393939) //導航欄標題顏色
#define Color_TabbarBg CKDarkMode?HexOf(0x1D213B):HexOf(0xffffff) //標籤欄背景顏色
#define Color_Selected CKDarkMode?HexOf(0x46CA8F):HexOf(0x46CA8F) //綠色 (按鈕選中、已認證狀態的顏色)
#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) //深藍色 (特殊顏色)
#define Color_HalfTitle CKDarkMode?Hex_A(0x999999, 0.5):Hex_A(0x999999, 0.5);//半透明文字 色值是副標題的一半
複製代碼

若是想關閉暗黑模式,直接設置:ide

#define CKDarkMode NO函數

.post

2、遇到的問題:

一、最開始我是用的self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark去作判斷,可是有些類並無UITraitCollection這個屬性,不少地方報錯。

解決方案:ui

改用UITraitCollection.current屬性來獲取當前App的顏色模式。spa

二、CGColorRef相關:

bt.layer.borderColor = Color_Selected.CGColor;
複製代碼

報錯:

Incompatible operand types ('UIColor * _Nonnull' and 'CGColorRef _Nonnull' (aka 'struct CGColor *'))

解決方案:

UIColor *color = Color_Selected;
bt.layer.borderColor = color.CGColor;
複製代碼

3.每次打開APP都能展現正常的模式;可是若是打開APP後再切換模式,已經加載出來的頁面依然會顯示切換以前的主題模式。

解決方案:
在頁面中添加通知,獲取到切換主題的通知後從新刷新一下頁面顏色(相似於項目中的國際化通知處理邏輯)

4.項目中個別頁面的狀態欄是固定的白色,在切換頁面的時候會把狀態欄切換回主題顏色黑色,可是在暗黑模式下就會有問題,由於暗黑模式下整個APP的狀態欄都是白色的,這時不須要切換回黑色。

解決方案:
添加一個UIStatusBarStyle變量記錄主題狀態欄顏色,這樣能夠不用在控制器內作太多額外的判斷。若是用 @available(iOS 13.0, *) 去作判斷,需求變動後還要每一個地方都去改動代碼。用了這種方式,後面即便更改了主題或者關閉了暗黑模式,也不用一一去改代碼;也能夠經過上面定義的宏CKDarkMode作判斷,關閉暗黑模式時只需把CKDarkMode設置爲NO就行

UIStatusBarStyle _themeStatusBarStyle;//記錄主題狀態欄顏色

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    _themeStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
    // 設置狀態欄顏色爲白色
    [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
}

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    // 恢復狀態欄顏色爲主題顏色
    [UIApplication sharedApplication].statusBarStyle = _themeStatusBarStyle;
}
複製代碼

五、使用了宏的地方都會報警告,提示我要作系統版本判斷,可是實際上我已經在CKDarkMode中判斷過了,系統檢測不到:

'currentTraitCollection' is only available on iOS 13.0 or newer

解決方案:使用UIColor擴展。
999+的警告有點影響代碼視覺體驗,後面應該會改用擴展的方式。若是有更好的解決方案請在下方留言。

.

3、UITraitCollection介紹:

一、在 iOS 13 中,咱們能夠經過 UITraitCollection 來判斷當前系統的模式。UIView 和 UIViewController 、UIScreen、UIWindow 都已經聽從了UITraitEnvironment這個協議,所以這些類都擁有一個叫作 traitCollection的屬性,在這些類中,咱們能夠這樣去判斷當前 App 的顏色模式:

BOOL isDark = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark);

二、另外,咱們還能夠經過 UITraitCollection.current這個屬性來獲取當前 App 的顏色模式。

三、若是暫時不想開放這個功能,能夠先暫時全局關閉暗黑模式:

在 Info.plist 文件中,添加 key 爲 User Interface Style,類型爲 String,value 設置爲 Light (Dark)便可,若是從新打開就把這條設置刪除。(這種方式是在APP整個生命週期內關閉了暗黑模式;上面的設置#define CKDarkMode NO只是用代碼作了判斷並只在用了宏的地方起做用)。

四、在 iOS 13中,UIView、UIViewController 、UIWindow 有了一個 overrideUserInterfaceStyle的新屬性,能夠覆蓋系統的模式。

單個頁面或視圖關閉暗黑模式,設置 overrideUserInterfaceStyle 爲對應的模式,強制限制該視圖與其子視圖以設置的模式進行展現,不跟隨系統模式改變進行改變。

self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;

設置此屬性會影響當前view / viewController / window 以及它下面的任何內容。
若是你但願一個子視圖監聽系統的模式,請將 overrideUserInterfaceStyle 屬性設置爲.unspecified

.

4、拓展

除了個人這種實現方案,還有其餘方案能夠適配暗黑模式:

一、UIColor擴展:

+(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;
    }
}
複製代碼

問題:
這種寫法要在每一個使用的地方分別傳一個普通模式的顏色和暗黑模式的顏色,不方便維護。

解決方案:
能夠定義幾個經常使用顏色函數,特殊的場景就用上面的方法,這樣就不須要在每一個地方都控制顏色值了。

+(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;
    }
}
複製代碼

二、能夠在 Images.xcassets 中定義幾種經常使用顏色,併爲顏色再配置一個用於暗黑模式的對應的顏色:

三、在 Images.xcassets 中配置不一樣模式下的圖片,當你設置爲暗黑模式後就會自動顯示對應的圖片

四、啓動圖:

LaunchScreen.storyboard能夠像普通的圖片那樣針對深色模式設置另外的一張圖片

五、layer:

UIColor *resolvedColor = [[UIColor labelColor] resolvedColorWithTraitCollection:self.view.traitCollection];
label.layer.borderColor = resolvedColor.CGColor;
複製代碼

六、UIActivityIndicatorView 的 style:

iOS 13前 的 UIActivityIndicatorViewStyle:

typedef NS_ENUM(NSInteger, UIActivityIndicatorViewStyle) {
    UIActivityIndicatorViewStyleWhiteLarge,
    UIActivityIndicatorViewStyleWhite,
    UIActivityIndicatorViewStyleGray,
};
複製代碼

iOS 13後,因爲暗黑模式,上述三個屬性都被廢棄,建議使用以下 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", 
};
複製代碼

七、Status Bar 的 style :

在 iOS 13 以前,狀態欄的樣式的枚舉值也帶有着明顯的顏色傾向,UIStatusBarStyleDefault、UIStatusBarStyleLightContent

如今,狀態欄的 default 樣式會根據當前的模式展現不一樣的顏色,而原有的 lightContent 樣式則新增一個 darkContent 的樣式與之對應。

typedef NS_ENUM(NSInteger, UIStatusBarStyle) {
    UIStatusBarStyleDefault      = 0, // Automatically chooses light or dark content based on the user interface style
    UIStatusBarStyleLightContent = 1, // Light content, for use on dark backgrounds
    UIStatusBarStyleDarkContent  = 3, // Dark content, for use on light backgrounds
};
複製代碼

八、SF Symbols

.

注意:

命名時要保證這個名字的全局惟一性,避免和項目中其餘命名雷同,這樣能夠保證全局搜索時搜索到的結果只有你想搜索的內容,便於維護。例如你取名RedColor,會搜索到不少其餘沒用的信息。這種命名思路也能夠用在其餘地方。

除了改背景顏色、文字顏色,還須要替換圖標、圖片,這個須要UI配合切圖。

參考:

How To Adopt Dark Mode In Your iOS App
DarkMode1
DarkMode2
DarkMode3

相關文章
相關標籤/搜索