iOS13 深色模式與淺色模式適配講解

iOS 13

2019年6月4日凌晨,蘋果在開發(fā)者大會上推出了新一代手機操作系統(tǒng) iOS 13,主要更新了照片應(yīng)用、滑動輸入和更多動畫表情,還有就是增加了”深色模式“,優(yōu)化了音量的調(diào)節(jié)方式。

深色模式”終于來了“。

在所有關(guān)于 iOS13 的更新項目里,“深色模式”是網(wǎng)友討論最多的。該模式可以根據(jù)日出日落時間自動開啟,開啟后,不只有壁紙,所有的系統(tǒng)元素都會變成暗色,起到在夜里降低屏幕亮度、保護用戶眼睛的作用。

Light Mode and Dark Mode

實際上,蘋果用戶很早就開始呼吁 iOS 加入這個深色模式。如今 iOS13 正式發(fā)布深色模式,媒體和網(wǎng)友評論的口吻都是“終于來了”,“遲到的深色模式”。

Dark Mode Apps

對于 iOS 程序猿們而言,如何適配 iOS13 系統(tǒng)的深色模式呢?我推薦從以下幾個方面去做適配,推薦參考項目 QPlayer 。

運用系統(tǒng) API

如何運用系統(tǒng) API,請閱讀這篇文章《iOS13 快速讀懂深色模式 API》。

顏色相關(guān)的適配

  • 不同模式的適配主要涉及顏色和圖片兩個方面的適配。
  • 其中顏色適配,包括相關(guān)背景色和字體顏色。
  • 當系統(tǒng)模式切換的時候,我們不需要如何操作,系統(tǒng)會自動渲染頁面,只需要做好不同模式的顏色和圖片即可。

UIColor

  • iOS13 之前 UIColor 只能表示一種顏色,從 iOS13 開始 UIColor 是一個動態(tài)的顏色,在不同模式下可以分別代表不同的顏色。
  • 下面是 iOS13 系統(tǒng)提供的動態(tài)顏色種類,使用以下顏色值,在模式切換時,則不需要做特殊處理。
@interface UIColor (UIColorSystemColors)
#pragma mark System colors

@property (class, nonatomic, readonly) UIColor *systemRedColor          API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemGreenColor        API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemBlueColor         API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemOrangeColor       API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemYellowColor       API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemPinkColor         API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemPurpleColor       API_AVAILABLE(ios(9.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemTealColor         API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemIndigoColor       API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
// 灰色種類, 在Light模式下, systemGray6Color更趨向于白色
@property (class, nonatomic, readonly) UIColor *systemGrayColor         API_AVAILABLE(ios(7.0), tvos(9.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);

#pragma mark Foreground colors
@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);
// 系統(tǒng)鏈接的前景色
@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);

#pragma mark Background colors
@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);

#pragma mark Fill colors
@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);

#pragma mark Other colors
// 這兩個是非動態(tài)顏色值
@property(class, nonatomic, readonly) UIColor *lightTextColor API_UNAVAILABLE(tvos);    // for a dark background
@property(class, nonatomic, readonly) UIColor *darkTextColor API_UNAVAILABLE(tvos);     // for a light background

@property(class, nonatomic, readonly) UIColor *groupTableViewBackgroundColor API_DEPRECATED_WITH_REPLACEMENT("systemGroupedBackgroundColor", ios(2.0, 13.0), tvos(13.0, 13.0));
@property(class, nonatomic, readonly) UIColor *viewFlipsideBackgroundColor API_DEPRECATED("", ios(2.0, 7.0)) API_UNAVAILABLE(tvos);
@property(class, nonatomic, readonly) UIColor *scrollViewTexturedBackgroundColor API_DEPRECATED("", ios(3.2, 7.0)) API_UNAVAILABLE(tvos);
@property(class, nonatomic, readonly) UIColor *underPageBackgroundColor API_DEPRECATED("", ios(5.0, 7.0)) API_UNAVAILABLE(tvos);

@end
  • 上面系統(tǒng)提供的這些顏色種類,根本不能滿足我們正常開發(fā)的需要,大部分的顏色值也都是自定義。
  • 系統(tǒng)也為我們提供了創(chuàng)建自定義顏色的方法。
@available(iOS 13.0, *)
public init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)

Objective-C

+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *traitCollection))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
- (UIColor *)initWithDynamicProvider:(UIColor * (^)(UITraitCollection *traitCollection))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
  • 上面的方法接受一個閉包 (block) 。
  • 當系統(tǒng)在 LightModeDarkMode 之間相互切換時就會自動觸發(fā)此回調(diào)。
  • 回調(diào)返回一個 UITraitCollection,可根據(jù)改對象判斷是那種模式。
fileprivate func getColor() -> UIColor {
    return UIColor { (collection) -> UIColor in
        if (collection.userInterfaceStyle == .dark) {
            return UIColor.red
        }
        return UIColor.green
    }
}

除了上述兩個方法之外,UIColor 還增加了一個實例方法。

// 通過當前 traitCollection 得到對應(yīng) UIColor
@available(iOS 13.0, *)
open func resolvedColor(with traitCollection: UITraitCollection) -> UIColor

CGColor

  • UIColor 只是設(shè)置背景色和文字顏色的類,可以動態(tài)的設(shè)置。
  • 可是如果是需要設(shè)置類似邊框顏色等屬性時,又該如何處理呢?
  • 設(shè)置上述邊框?qū)傩?,需要用?CGColor 類,但是在 iOS13CGColor 并不是動態(tài)顏色值,只能表示一種顏色。
  • 在監(jiān)聽模式改變的方法中traitCollectionDidChange,根據(jù)不同的模式進行處理。
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {

    super.traitCollectionDidChange(previousTraitCollection)
    
    // 每次模式改變的時候, 這里都會執(zhí)行
    if (previousTraitCollection?.userInterfaceStyle == .dark) {
        redView.layer.borderColor = UIColor.red.cgColor
    } else {
        redView.layer.borderColor = UIColor.green.cgColor
    }
}

圖片適配

  • iOS 中,圖片基本都是放在 Assets.xcassets 里面,所以圖片的適配,我們就相對麻煩一些了。
  • 正常情況下都是下面這中處理方式。
Appearances None
  • 需要適配不同模式的情況下,需要兩套不同的圖片,并做如下設(shè)置。
  • 在設(shè)置 Appearances 時,我們選擇 Any, Dark 就可以了 (只需要適配深色模式和非深色模式)。
Appearances Dark

適配相關(guān)

當前頁面模式

  • 原項目中如果沒有適配 Dark Mode,當你切換到 Dark Mode 后,你可能會發(fā)現(xiàn)有些部分頁面的顏色自動適配了。
  • 未設(shè)置過背景顏色或者文字顏色的組件,在 Dark Mode 模式下,就是黑色的。
  • 這里我們就需要真對該單獨 App 強制設(shè)置成 Light Mode 模式。
// 設(shè)置改屬性, 只會影響當前的視圖, 不會影響前面的 controller 和后續(xù) present 的 controller
@available(iOS 13.0, *)
open var overrideUserInterfaceStyle: UIUserInterfaceStyle

// 使用示例
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    
    // 每次模式改變的時候, 這里都會執(zhí)行
    if (previousTraitCollection?.userInterfaceStyle == .dark) {
        // 在Dark模式下, 強制改成Light模式
        overrideUserInterfaceStyle = .light
    }
}

強制項目的顯示模式

  • 上面這種方式只能針對某一個頁面修改,如果需要對整個項目禁用 Dark 模式。
  • 可以通過修改 windowoverrideUserInterfaceStyle 屬性。
  • Xcode11 創(chuàng)建的項目中,windowAppDelegate 移到 SceneDelegate 中,添加下面這段代碼,就會做到全局修改顯示模式。
let scene = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
scene?.window?.overrideUserInterfaceStyle = .light

在之前的項目中,可以在 AppDelegate 設(shè)置如下代碼:

window.overrideUserInterfaceStyle = .light

我創(chuàng)建的項目,上述代碼的確會強制改變當前的模式,但是狀態(tài)欄的顯示不會被修改。

終極方案

  • 需要在 info.plist 文件中添加 User Interface Style 配置,并設(shè)置為 Light
<key>UIUserInterfaceStyle</key>
<string>Light</string>

問題又來了,即使做了上面的修改,在 React Native 中,狀態(tài)欄的依然是根據(jù)不同的模式顯示不同的顏色,該如何處理?

Status Bar 更新

iOS13 中蘋果對于 Status Bar 也做了部分修改,在 iOS13 之前

public enum UIStatusBarStyle : Int {
    case `default` // 默認文字黑色

    @available(iOS 7.0, *)
    case lightContent // 文字白色
}

iOS13 開始 UIStatusBarStyle 一共有三種狀態(tài)

public enum UIStatusBarStyle : Int {
    case `default` // 自動選擇黑色或白色

    @available(iOS 7.0, *)
    case lightContent // 文字白色
    
    @available(iOS 13.0, *)
    case darkContent // 文字黑色
}

React Native 的代碼中,設(shè)置狀態(tài)欄的顏色為黑色,代碼如下:

<StatusBar barStyle={'dark-content'} />
  • 上面這段代碼在 iOS13 系統(tǒng)的手機中是無效的。
  • 雖然上面的代碼中設(shè)置了 dark-content 模式,但是在 iOS 原生代碼中 dark-content 實際是 `UIStatusBarStyleDefault。
  • 在文件 RCTStatusBarManager.m
RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{
  @"default": @(UIStatusBarStyleDefault),
  @"light-content": @(UIStatusBarStyleLightContent),
  @"dark-content": @(UIStatusBarStyleDefault),
}), UIStatusBarStyleDefault, integerValue);

修改上面代碼即可

@"dark-content": @(@available(iOS 13.0, *) ? UIStatusBarStyleDarkContent : UIStatusBarStyleDefault),

iOS13 其他更新

Apple 登錄

Sign In with Apple will be available for beta testing this summer. It will be required as an option for users in apps that support third-party sign-in when it is commercially available later this year.

  • 如果 APP 支持三方登陸 (FacbookGoogle、微信、QQ、支付寶等),就必須支持 Apple 登陸,且要放置前邊。
  • 至于 Apple 登錄按鈕的樣式,建議支持使用 Apple 提供的按鈕樣式,已經(jīng)適配各類設(shè)備,可參考 Sign In with Apple 。

LaunchImage

即將被廢棄的 LaunchImage。

  • iOS 8 的時候,蘋果就引入了 LaunchScreen,我們可以設(shè)置 LaunchScreen 來作為啟動頁。
  • 現(xiàn)在你還可以使用 LaunchImage 來設(shè)置啟動圖,但是隨著蘋果設(shè)備尺寸越來越多,適配顯然相對麻煩一些。
  • 使用 LaunchScreen 的話,情況會變的很簡單,LaunchScreen是支持 AutoLayoutSizeClass 的,所以適配各種屏幕都不在話下。
  • ??從2020年4月開始,所有 App 將必須提供 LaunchScreen,而 LaunchImage 即將退出歷史舞臺。

UIWebView

'UIWebView' was deprecated in iOS 12.0: No longer supported; please adopt WKWebView.

iOS 13 開始也不再支持 UIWebView 控件了,請盡快采用 WKWebView。

@available(iOS, introduced: 2.0, deprecated: 12.0, message: "No longer supported; please adopt WKWebView.")
open class UIWebView : UIView, NSCoding, UIScrollViewDelegate { }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容