如果是老項目,改動太多,不想適配暗黑模式的話,有個偷懶的方法。或者還沒適配完又不想給用戶看,可以先暫時全局關閉暗黑模式:在 Info.plist 文件中,添加 key 爲 User Interface Style
,類型爲 String,value 設置爲 Light
即可。
追求極致體驗,就要完全適配,所有頁面沒適配好的統統適配一遍,分爲以下幾種情況。
- 一、適配Dark Mode
- 顏色適配
- 圖片適配
- 二、獲取當前模式(Light or Dark)
- 三、其他內容
- 四、總結
首先看看我們的效果圖:
適配效果圖
一、適配Dark Mode
開發者主要從顏色和圖片兩個方面進行適配,我們不需要關心切換模式時該如何操作,這些都由系統幫我們實現
1 顏色適配
- iOS13 之前
UIColor
只能表示一種顏色,而從 iOS13 開始UIColor
是一個動態的顏色,在Light Mode
和Dark Mode
可以分別設置不同的顏色。 - iOS13系統提供了一些動態顏色
@property (class, nonatomic, readonly) UIColor *systemBrownColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemIndigoColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemGray2Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray3Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray4Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray5Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray6Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *labelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *secondaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *tertiaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *quaternaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *linkColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *placeholderTextColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *separatorColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *opaqueSeparatorColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *quaternarySystemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
① 實例
[self.view setBackgroundColor:[UIColor systemBackgroundColor]];
[self.titleLabel setTextColor:[UIColor labelColor]];
[self.detailLabel setTextColor:[UIColor placeholderTextColor]];
② 效果展示
系統UIColor樣式
用法和iOS13之前的一樣,使用系統提供的這些動態顏色,不需要其他的適配操作
③ 自定義動態UIColor
在實際開發過程,系統提供的這些顏色還遠遠不夠,因此我們需要創建更多的動態顏色
初始化動態UIColor方法
iOS13 UIColor
增加了兩個初始化方法,使用以下方法可以創建動態UIColor
注:一個是類方法,一個是實例方法
+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
- (UIColor *)initWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
- 這兩個方法要求傳一個
block
進去 - 當系統在
LightMode
和DarkMode
之間相互切換時就會觸發此回調 - 這個
block
會返回一個UITraitCollection
類 - 我們需要使用其屬性
userInterfaceStyle
,它是一個枚舉類型,會告訴我們當前是LightMode
還是DarkMode
typedef NS_ENUM(NSInteger, UIUserInterfaceStyle) {
UIUserInterfaceStyleUnspecified,
UIUserInterfaceStyleLight,
UIUserInterfaceStyleDark,
} API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos);
實例
UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
return [UIColor redColor];
}
else {
return [UIColor greenColor];
}
}];
[self.bgView setBackgroundColor:dyColor];
效果展示
自定義UIColor效果
接下來我們看看如何適配圖片
2.圖片適配
- 打開
Assets.xcassets
- 新建一個
Image set
默認顯示效果
- 打開右側工具欄,點擊最後一欄,找到
Appearances
,選擇Any,Dark
側邊欄
- 將兩種模式下不同的圖片資源都拖進去
兩種不同模式
- 使用該圖片
[_logoImage setImage:[UIImage imageNamed:@"icon_logo"]];
最終效果圖
大功告成,完成顏色和圖片的Dark Mode
適配,是不是很easy呢
① 獲取當前模式(Light or Dark)
有時候我們需要知道當前處於什麼模式,並根據不同的模式執行不同的操作
iOS13中CGColor
依然只能表示單一的顏色
通過調用UITraitCollection.currentTraitCollection.userInterfaceStyle
獲取當前模式
實例
if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
[self.titleLabel setText:@"DarkMode"];
}
else {
[self.titleLabel setText:@"LightMode"];
}
三、其他
1.監聽模式切換
有時我們需要監聽系統模式的變化,並作出響應
那麼我們就需要在需要監聽的viewController中,重寫下列函數
// 注意:參數爲變化前的traitCollection
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;
// 判斷兩個UITraitCollection對象是否不同
- (BOOL)hasDifferentColorAppearanceComparedToTraitCollection:(UITraitCollection *)traitCollection;
① 示例
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
// trait發生了改變
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
// 執行操作
}
}
2.CGColor適配
我們知道iOS13後,
UIColor
能夠表示動態顏色,但是CGColor依然只能表示一種顏色,那麼對於CALayer等對象如何適配暗黑模式呢?當然是利用上一節提到的監聽模式切換的方法啦。
① 方式一:resolvedColor
// 通過當前traitCollection得到對應UIColor
// 將UIColor轉換爲CGColor
- (UIColor *)resolvedColorWithTraitCollection:(UITraitCollection *)traitCollection;
實例
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
return [UIColor redColor];
}
else {
return [UIColor greenColor];
}
}];
UIColor *resolvedColor = [dyColor resolvedColorWithTraitCollection:previousTraitCollection];
layer.backgroundColor = resolvedColor.CGColor;
② 方式二:performAsCurrent
// 使用當前trainCollection調用此方法
- (void)performAsCurrentTraitCollection:(void (^)(void))actions;
示例
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
return [UIColor redColor];
}
else {
return [UIColor greenColor];
}
}];
[self.traitCollection performAsCurrentTraitCollection:^{
layer.backgroundColor = dyColor.CGColor;
}];
}
方式三:最簡單的方法
直接設置爲一個動態UIColor的CGColor即可
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
return [UIColor redColor];
}
else {
return [UIColor greenColor];
}
}];
layer.backgroundColor = dyColor.CGColor;
}
⚠️!!! 設置layer顏色都是在traitCollectionDidChange
中,意味着如果沒有發生模式切換,layer將會沒有顏色,需要設置一個基本顏色
3.模式切換時打印log
模式切換時自動打印log,就不需要我們一次又一次的執行po命令了
- 在Xcode菜單欄
Product
->Scheme
->Edit Scheme
- 選擇
Run
->Arguments
->Arguments Passed On Launch
- 添加以下命令即可
-UITraitCollectionChangeLoggingEnabled YES
-
模式切換打印log
4.強行設置App模式
當系統設置爲
Light Mode
時,對某些App的個別頁面希望一直顯示Dark Mode
下的樣式,這個時候就需要強行設置當前ViewController
的模式了
// 設置當前view或viewCongtroller的模式
@property(nonatomic) UIUserInterfaceStyle overrideUserInterfaceStyle;
示例
// 設置爲Dark Mode即可
[self setOverrideUserInterfaceStyle:UIUserInterfaceStyleDark];
⚠️ 注意!!!
- 當我們強行設置當前viewController爲Dark Mode後,這個viewController下的view都是Dark Mode
- 由這個ViewController present出的ViewController不會受到影響,依然跟隨系統的模式
- 要想一鍵設置App下所有的ViewController都是Dark Mode,請直接在Window上執行
overrideUserInterfaceStyle
- 對
window.rootViewController
強行設置Dark Mode
也不會影響後續present出的ViewController的模式
5.NSAttributedString優化
對於UILabel、UITextField、UITextView,在設置NSAttributedString時也要考慮適配
Dark Mode
,否則在切換模式時會與背景色融合,造成不好的體驗
不建議的做法
NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16]};
NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
推薦的做法
// 添加一個NSForegroundColorAttributeName屬性
NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:[UIColor labelColor]};
NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
五、總結
總的來說,iOS13主要有以下變化:
1.支持 Dark Mode
2.UIColor變爲動態顏色
3.更新StatusBar樣式
4.更新UIActivityIndicatorView樣式
完整iOS13新特性請參考以下文章:
iOS13 新特性簡介
iOS13-適配夜間模式/深色外觀(Dark Mode)
iOS13 暗黑模式(Dark Mode)適配之OC版