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

今天的 WWDC 19 上發(fā)布了 iOS 13,我們來(lái)看下如何適配 DarkMode

首先我們來(lái)看下效果圖


效果圖.gif

如何適配 DarkMode

DarkMode 主要從兩個(gè)方面來(lái)適配,一是顏色,二是圖片,適配的代碼不是很多,接下來(lái)讓我們一起來(lái)看看具體是怎么操作的吧。

顏色適配

iOS 13 之前 UIColor 只能表示一種顏色,從 iOS 13 開(kāi)始 UIColor 是一個(gè)動(dòng)態(tài)的顏色,它可以在 LightMode 和 DarkMode 擁有不同的顏色。
iOS 13 下 UIColor 增加了很多動(dòng)態(tài)顏色,我們來(lái)看下用系統(tǒng)提供的顏色能實(shí)現(xiàn)怎么樣的效果。

// 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
效果圖

怎么樣,看起來(lái)和 iOS 13 之前設(shè)置一個(gè)顏色的方法一樣吧,用這種動(dòng)態(tài)顏色,系統(tǒng)直接替我們完成了適配的工作,是不是很方便呢。

如何自己創(chuàng)建一個(gè)動(dòng)態(tài)的 UIColor

上面我們說(shuō)到系統(tǒng)提供了一些動(dòng)態(tài)的顏色供我們使用,但是在正常開(kāi)發(fā)中,系統(tǒng)提供的顏色肯定是不夠用的,所以我們要自己創(chuàng)建動(dòng)態(tài)顏色。

iOS 13 下 UIColor 增加了一個(gè)初始化方法,我們可以用這個(gè)初始化方法來(lái)創(chuàng)建動(dòng)態(tài)顏色。

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

這個(gè)方法要求傳一個(gè)閉包進(jìn)去,當(dāng)系統(tǒng)從 LightMode 和 DarkMode 之間切換的時(shí)候就會(huì)觸發(fā)這個(gè)回調(diào)。
這個(gè)閉包返回一個(gè) UITraitCollection 類(lèi),我們要用這個(gè)類(lèi)的 userInterfaceStyle 屬性。
userInterfaceStyle 是一個(gè)枚舉,聲明如下

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

這個(gè)枚舉會(huì)告訴我們當(dāng)前是 LightMode or DarkMode


現(xiàn)在我們創(chuàng)建兩個(gè) 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

現(xiàn)在,我們做完了動(dòng)圖中背景色和文本顏色的適配,接下來(lái)我們看看圖片如何適配

圖片適配

打開(kāi) Assets.xcassets
把圖片拖拽進(jìn)去,我們可以看到這樣的頁(yè)面

然后我們?cè)谟覀?cè)工具欄中點(diǎn)擊最后一欄,點(diǎn)擊 Appearances 選擇 Any, Dark,如圖所示

我們把 DarkMode 的圖片拖進(jìn)去,如圖所示

最后我們加上 ImageView 的代碼

imageView.image = UIImage(named: "icon")

現(xiàn)在我們就已經(jīng)完成顏色和圖片的 DarkMode 適配,是不是很簡(jiǎn)單呢 (手動(dòng)滑稽)


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

我們可以看到,不管是顏色還是圖片,適配都是系統(tǒng)完成的,我們不用關(guān)心現(xiàn)在是什么樣的樣式。
但是在某些場(chǎng)景下,我們可能會(huì)有根據(jù)當(dāng)前樣式來(lái)做一些其他適配的需求,這時(shí)我們就需要知道現(xiàn)在什么樣式。
我們可以在 UIViewControllerUIView 中調(diào)用 traitCollection.userInterfaceStyle 來(lái)獲取當(dāng)前視圖的樣式,代碼如下

if trainCollection.userInterfaceStyle == .dark {
    // Dark
} else {
    // Light
}

那么我們什么時(shí)候需要用這樣的方法做適配呢,比如說(shuō)當(dāng)我們使用 CGColor 的時(shí)候,上面說(shuō)到 UIColor 在 iOS 13 下變成了一個(gè)動(dòng)態(tài)顏色,但是 CGColor 仍然只能表示單一的顏色,所以當(dāng)我們使用到 CGColor 的時(shí)候,我們就可以用上面的方法做適配。

顏色

對(duì)于 CGColor 我們還有還有另一種適配方法,代碼如下

let resolvedColor = labelColor.resolvedColor(with: traitCollection)
layer.borderColor = resolvedColor.cgColor

resolvedColor 方法會(huì)根據(jù)傳遞進(jìn)去的 traitCollection 返回對(duì)應(yīng)的顏色。

圖片

對(duì)于 UIImage 我們也有類(lèi)似的方法,代碼如下

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

如何監(jiān)聽(tīng)模式變化

上面我們說(shuō)了如何獲取當(dāng)前模式,但是我們要搭配監(jiān)聽(tīng)方法一起使用,當(dāng) light dark 模式切換的時(shí)候,要把上面的代碼再執(zhí)行一遍。系統(tǒng)為我們提供了一個(gè)回調(diào)方法,當(dāng) light dark 切換時(shí)就會(huì)觸發(fā)這個(gè)方法。

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

題外話(huà)
如果你覺(jué)得這樣為 CGColor 做適配很麻煩,那么不妨試試 XYColor 這個(gè)框架。

如何改變當(dāng)前模式

我們可以看到在動(dòng)圖中是直接改系統(tǒng)的模式,從而讓 App 的模式修改,但是對(duì)于某些有夜間模式功能的 App 來(lái)說(shuō),如果用戶(hù)打開(kāi)了夜間模式,那么即使現(xiàn)在系統(tǒng)是 light 模式,也要強(qiáng)制用 dark 模式。
我們可以用以下代碼將當(dāng)前 UIViewControllerUIView 的模式。

overrideUserInterfaceStyle = .dark
print(traitCollection.userInterfaceStyle)  // dark

我們可以看到設(shè)置了 overrideUserInterfaceStyle 之后,traitCollection.userInterfaceStyle 就是我們?cè)O(shè)置后的模式了。

需要給每一個(gè) Controller 和 View 都設(shè)置一遍嗎

答案是不需要,我們先來(lái)看一張圖。


當(dāng)我們?cè)O(shè)置一個(gè) controller 為 dark 之后,這個(gè) controller 下的 view,都會(huì)是 dark mode,但是后續(xù) present 的 controller 仍然是跟隨系統(tǒng)的樣式。

因?yàn)樘O(píng)果對(duì) overrideUserInterfaceStyle 屬性的解釋是這樣的。
當(dāng)我們?cè)谝粋€(gè)普通的 controlle, view 上重寫(xiě)這個(gè)屬性,只會(huì)影響當(dāng)前的視圖,不會(huì)影響前面的 controller 和后續(xù) present 的 controller。
但是當(dāng)我們?cè)?window 上設(shè)置 overrideUserInterfaceStyle 的時(shí)候,就會(huì)影響 window 下所有的 controller, view,包括后續(xù)推出的 controller。

但是當(dāng)我們?cè)?window.rootViewController 上設(shè)置 overrideUserInterfaceStyle 的時(shí)候,就會(huì)影響 rootViewController 下所有的 controller, view,包括后續(xù)推出的 controller。 感謝 hostname 指出錯(cuò)誤

我們回到剛剛的問(wèn)題上,如果 App 打開(kāi)夜間模式,那么很簡(jiǎn)單我們只需要設(shè)置 windowoverrideUserInterfaceStyle 屬性就好了。

題外話(huà):當(dāng)我們用 Xcode11 創(chuàng)建項(xiàng)目,我們會(huì)發(fā)現(xiàn)項(xiàng)目結(jié)構(gòu)發(fā)生了變化,windowAppDelegate 移到 SceneDelegate 中。
那么如何獲取 SceneDelegate 中的 window 呢,代碼如下

// 這里就簡(jiǎn)單介紹一下,實(shí)際項(xiàng)目中,如果是iOS應(yīng)用這么寫(xiě)沒(méi)問(wèn)題,但是對(duì)于iPadOS應(yīng)用還需要判斷scene的狀態(tài)是否激活
let scene = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
scene?.window?.overrideUserInterfaceStyle = .dark

其他內(nèi)容

Status Bar

之前 Status Bar 有兩種狀態(tài),defaultlightContent
現(xiàn)在 Status Bar 有三種狀態(tài),default, darkContentlightContent
現(xiàn)在的 darkContent 對(duì)應(yīng)之前的 default,現(xiàn)在的 default 會(huì)根據(jù)情況自動(dòng)選擇 darkContentlightContent

UIActivityIndicatorView

之前的 UIActivityIndicatorView 有三種 style 分別為 whiteLarge, whitegray,現(xiàn)在全部廢棄。
增加兩種 style 分別為 mediumlarge,指示器顏色用 color 屬性修改。

如何在模式切換時(shí)打印日志

Arguments 中的 Arguments Passed On Launch 里面添加下面這行命令。
-UITraitCollectionChangeLoggingEnabled YES


以上是 iOS 13 如何適配 Dark Mode 的全部?jī)?nèi)容,如有錯(cuò)誤歡迎指出。
WWDC鏈接 Implementing Dark Mode on iOS

如果你想知道 iOS 13 還增加了什么新特性可以閱讀這篇文章。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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