iOS13-適配夜間模式/深色外觀(Dark Mode)

今天的 WWDC 19 上發佈了 iOS 13,咱們來看下如何適配 DarkModegit

首先咱們來看下效果圖 github

效果圖.gif

如何適配 DarkMode

DarkMode 主要從兩個方面來適配,一是顏色,二是圖片,適配的代碼不是不少,接下來讓咱們一塊兒來看看具體是怎麼操做的吧。swift

顏色適配

iOS 13 以前 UIColor 只能表示一種顏色,從 iOS 13 開始 UIColor 是一個動態的顏色,它能夠在 LightMode 和 DarkMode 擁有不一樣的顏色。api

iOS 13 下 UIColor 增長了不少動態顏色,咱們來看下用系統提供的顏色能實現怎麼樣的效果。bash

// UIColor 增長的顏色
@available(iOS 13.0, *)
open class var systemBackground: UIColor { get }
@available(iOS 13.0, *)
open class var label: UIColor { get }
@available(iOS 13.0, *)
open class var placeholderText: UIColor { get }
...

view.backgroundColor = UIColor.systemBackground
label.textColor = UIColor.label
placeholderLabel.textColor = UIColor.placeholderText
複製代碼

效果圖

怎麼樣,看起來和 iOS 13 以前設置一個顏色的方法同樣吧,用這種動態顏色,系統直接替咱們完成了適配的工做,是否是很方便呢。閉包

如何本身建立一個動態的 UIColor

上面咱們說到系統提供了一些動態的顏色供咱們使用,可是在正常開發中,系統提供的顏色確定是不夠用的,因此咱們要本身建立動態顏色。app

iOS 13 下 UIColor 增長了一個初始化方法,咱們能夠用這個初始化方法來建立動態顏色。框架

@available(iOS 13.0, *)
public init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)
複製代碼

這個方法要求傳一個閉包進去,當系統從 LightMode 和 DarkMode 之間切換的時候就會觸發這個回調。ide

這個閉包返回一個 UITraitCollection 類,咱們要用這個類的 userInterfaceStyle 屬性。 userInterfaceStyle 是一個枚舉,聲明以下工具

@available(iOS 12.0, *)
public enum UIUserInterfaceStyle : Int {
    case unspecified
    case light
    case dark
}
複製代碼

這個枚舉會告訴咱們當前是 LightMode or DarkMode


如今咱們建立兩個 UIColor 並賦值給 view.backgroundColorlabel,代碼以下

let backgroundColor = UIColor { (trainCollection) -> UIColor in
    if trainCollection.userInterfaceStyle == .dark {
        return UIColor.black
    } else {
        return UIColor.white
    }
}
view.backgroundColor = backgroundColor

let labelColor = UIColor { (trainCollection) -> UIColor in
    if trainCollection.userInterfaceStyle == .dark {
        return UIColor.white
    } else {
        return UIColor.black
    }
}
label.textColor = labelColor
複製代碼

如今,咱們作完了動圖中背景色和文本顏色的適配,接下來咱們看看圖片如何適配

圖片適配

打開 Assets.xcassets 把圖片拖拽進去,咱們能夠看到這樣的頁面

而後咱們在右側工具欄中點擊最後一欄,點擊 Appearances 選擇 Any, Dark,如圖所示

咱們把 DarkMode 的圖片拖進去,如圖所示

最後咱們加上 ImageView 的代碼

imageView.image = UIImage(named: "icon")
複製代碼

如今咱們就已經完成顏色和圖片的 DarkMode 適配,是否是很簡單呢 (手動滑稽)

如何獲取當前模式 (Light or Dark)

咱們能夠看到,不論是顏色仍是圖片,適配都是系統完成的,咱們不用關心如今是什麼樣的樣式。

可是在某些場景下,咱們可能會有根據當前樣式來作一些其餘適配的需求,這時咱們就須要知道如今什麼樣式。

咱們能夠在 UIViewControllerUIView 中調用 traitCollection.userInterfaceStyle 來獲取當前視圖的樣式,代碼以下

if trainCollection.userInterfaceStyle == .dark {
    // Dark
} else {
    // Light
}
複製代碼

那麼咱們何時須要用這樣的方法作適配呢,好比說當咱們使用 CGColor 的時候,上面說到 UIColor 在 iOS 13 下變成了一個動態顏色,可是 CGColor 仍然只能表示單一的顏色,因此當咱們使用到 CGColor 的時候,咱們就能夠用上面的方法作適配。

顏色

對於 CGColor 咱們還有還有另外一種適配方法,代碼以下

let resolvedColor = labelColor.resolvedColor(with: traitCollection)
layer.borderColor = resolvedColor.cgColor
複製代碼

resolvedColor 方法會根據傳遞進去的 traitCollection 返回對應的顏色。

圖片

對於 UIImage 咱們也有相似的方法,代碼以下

let image = UIImage(named: "icon")
let resovledImage = image?.imageAsset?.image(with: traitCollection)
複製代碼

如何監聽模式變化

上面咱們說了如何獲取當前模式,可是咱們要搭配監聽方法一塊兒使用,當 light dark 模式切換的時候,要把上面的代碼再執行一遍。系統爲咱們提供了一個回調方法,當 light dark 切換時就會觸發這個方法。

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
        // 適配代碼
    }
}
複製代碼

題外話
若是你以爲這樣爲 CGColor 作適配很麻煩,那麼不妨試試 XYColor 這個框架。

如何改變當前模式

咱們能夠看到在動圖中是直接改系統的模式,從而讓 App 的模式修改,可是對於某些有夜間模式功能的 App 來講,若是用戶打開了夜間模式,那麼即便如今系統是 light 模式,也要強制用 dark 模式。

咱們能夠用如下代碼將當前 UIViewControllerUIView 的模式。

overrideUserInterfaceStyle = .dark
print(traitCollection.userInterfaceStyle)  // dark
複製代碼

咱們能夠看到設置了 overrideUserInterfaceStyle 以後,traitCollection.userInterfaceStyle 就是咱們設置後的模式了。

須要給每個 Controller 和 View 都設置一遍嗎

答案是不須要,咱們先來看一張圖。

當咱們設置一個 controller 爲 dark 以後,這個 controller 下的 view,都會是 dark mode, 可是後續推出的 controller 仍然是跟隨系統的樣式。

由於蘋果對 overrideUserInterfaceStyle 屬性的解釋是這樣的。

當咱們在一個普通的 controlle, view 上重寫這個屬性,只會影響當前的視圖,不會影響前面的 controller 和後續推出的 controller。

可是當咱們在 window 上設置 overrideUserInterfaceStyle 的時候,就會影響 window 下全部的 controller, view,包括後續推出的 controller。

咱們回到剛剛的問題上,若是 App 打開夜間模式,那麼很簡單咱們只須要設置 windowoverrideUserInterfaceStyle 屬性就行了。

題外話: 當咱們用 Xcode11 建立項目,咱們會發現項目結構發生了變化,windowAppDelegate 移到 SceneDelegate 中。那麼如何獲取 SceneDelegate 中的 window 呢,代碼以下

// 這裏就簡單介紹一下,實際項目中,若是是iOS應用這麼寫沒問題,可是對於iPadOS應用還須要判斷scene的狀態是否激活
let scene = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
scene?.window?.overrideUserInterfaceStyle = .dark
複製代碼

其餘內容

Status Bar

以前 Status Bar 有兩種狀態,defaultlightContent

如今 Status Bar 有三種狀態,default, darkContentlightContent

如今的 darkContent 對應以前的 default,如今的 default 會根據狀況自動選擇 darkContentlightContent

UIActivityIndicatorView

以前的 UIActivityIndicatorView 有三種 style 分別爲 whiteLarge, whitegray如今所有廢棄

增長兩種 style 分別爲 mediumlarge,指示器顏色用 color 屬性修改。

如何在模式切換時打印日誌

Arguments 中的 Arguments Passed On Launch 裏面添加下面這行命令。

-UITraitCollectionChangeLoggingEnabled YES


以上是 iOS 13 如何適配 Dark Mode 的所有內容,若有錯誤歡迎指出。

WWDC連接 Implementing Dark Mode on iOS

若是你想知道 iOS 13 還增長了什麼新特性能夠閱讀這篇文章

相關文章
相關標籤/搜索