iOS13適配之暗黑模式(Dark Mode)

如果是老項目,改動太多,不想適配暗黑模式的話,有個偷懶的方法。或者還沒適配完又不想給用戶看,可以先暫時全局關閉暗黑模式:在 Info.plist 文件中,添加 key 爲 User Interface Style,類型爲 String,value 設置爲 Light 即可。

追求極致體驗,就要完全適配,所有頁面沒適配好的統統適配一遍,分爲以下幾種情況。

  • 一、適配Dark Mode
    • 顏色適配
    • 圖片適配
  • 二、獲取當前模式(Light or Dark)
  • 三、其他內容
  • 四、總結

首先看看我們的效果圖:

適配效果圖

一、適配Dark Mode

開發者主要從顏色和圖片兩個方面進行適配,我們不需要關心切換模式時該如何操作,這些都由系統幫我們實現

1 顏色適配

  • iOS13 之前 UIColor只能表示一種顏色,而從 iOS13 開始UIColor是一個動態的顏色,在Light ModeDark 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進去
  • 當系統在LightModeDarkMode之間相互切換時就會觸發此回調
  • 這個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];

⚠️ 注意!!!

  • 當我們強行設置當前viewControllerDark 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版

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章