今天的 WWDC 19 上發(fā)布了 iOS 13峭竣,我們來看下如何適配 DarkMode
首先我們來看下效果圖
如何適配 DarkMode
DarkMode 主要從兩個方面來適配,一是顏色捧灰,二是圖片佳鳖,適配的代碼不是很多说榆,接下來讓我們一起來看看具體是怎么操作的吧泽腮。
顏色適配
iOS 13 之前 UIColor
只能表示一種顏色画髓,從 iOS 13 開始 UIColor
是一個動態(tài)的顏色录粱,它可以在 LightMode 和 DarkMode 擁有不同的顏色腻格。
iOS 13 下 UIColor
增加了很多動態(tài)顏色,我們來看下用系統(tǒng)提供的顏色能實現(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
怎么樣荒叶,看起來和 iOS 13 之前設(shè)置一個顏色的方法一樣吧,用這種動態(tài)顏色输虱,系統(tǒng)直接替我們完成了適配的工作些楣,是不是很方便呢。
如何自己創(chuàng)建一個動態(tài)的 UIColor
上面我們說到系統(tǒng)提供了一些動態(tài)的顏色供我們使用宪睹,但是在正常開發(fā)中愁茁,系統(tǒng)提供的顏色肯定是不夠用的,所以我們要自己創(chuàng)建動態(tài)顏色亭病。
iOS 13 下 UIColor
增加了一個初始化方法鹅很,我們可以用這個初始化方法來創(chuàng)建動態(tài)顏色。
@available(iOS 13.0, *)
public init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)
這個方法要求傳一個閉包進(jìn)去罪帖,當(dāng)系統(tǒng)從 LightMode 和 DarkMode 之間切換的時候就會觸發(fā)這個回調(diào)促煮。
這個閉包返回一個 UITraitCollection
類邮屁,我們要用這個類的 userInterfaceStyle
屬性。
userInterfaceStyle
是一個枚舉菠齿,聲明如下
@available(iOS 12.0, *)
public enum UIUserInterfaceStyle : Int {
case unspecified
case light
case dark
}
這個枚舉會告訴我們當(dāng)前是 LightMode or DarkMode
現(xiàn)在我們創(chuàng)建兩個 UIColor
并賦值給 view.backgroundColor
和 label
佑吝,代碼如下
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)在,我們做完了動圖中背景色和文本顏色的適配绳匀,接下來我們看看圖片如何適配
圖片適配
打開 Assets.xcassets
把圖片拖拽進(jìn)去芋忿,我們可以看到這樣的頁面
然后我們在右側(cè)工具欄中點擊最后一欄,點擊 Appearances
選擇 Any, Dark
疾棵,如圖所示
我們把 DarkMode 的圖片拖進(jìn)去戈钢,如圖所示
最后我們加上 ImageView
的代碼
imageView.image = UIImage(named: "icon")
現(xiàn)在我們就已經(jīng)完成顏色和圖片的 DarkMode 適配,是不是很簡單呢 (手動滑稽)
如何獲取當(dāng)前模式 (Light or Dark)
我們可以看到是尔,不管是顏色還是圖片殉了,適配都是系統(tǒng)完成的,我們不用關(guān)心現(xiàn)在是什么樣的樣式拟枚。
但是在某些場景下宣渗,我們可能會有根據(jù)當(dāng)前樣式來做一些其他適配的需求,這時我們就需要知道現(xiàn)在什么樣式梨州。
我們可以在 UIViewController
或 UIView
中調(diào)用 traitCollection.userInterfaceStyle
來獲取當(dāng)前視圖的樣式,代碼如下
if trainCollection.userInterfaceStyle == .dark {
// Dark
} else {
// Light
}
那么我們什么時候需要用這樣的方法做適配呢田轧,比如說當(dāng)我們使用 CGColor
的時候暴匠,上面說到 UIColor
在 iOS 13 下變成了一個動態(tài)顏色,但是 CGColor
仍然只能表示單一的顏色傻粘,所以當(dāng)我們使用到 CGColor
的時候每窖,我們就可以用上面的方法做適配。
顏色
對于 CGColor
我們還有還有另一種適配方法弦悉,代碼如下
let resolvedColor = labelColor.resolvedColor(with: traitCollection)
layer.borderColor = resolvedColor.cgColor
resolvedColor
方法會根據(jù)傳遞進(jìn)去的 traitCollection
返回對應(yīng)的顏色窒典。
圖片
對于 UIImage
我們也有類似的方法,代碼如下
let image = UIImage(named: "icon")
let resovledImage = image?.imageAsset?.image(with: traitCollection)
如何監(jiān)聽模式變化
上面我們說了如何獲取當(dāng)前模式稽莉,但是我們要搭配監(jiān)聽方法一起使用瀑志,當(dāng) light dark 模式切換的時候,要把上面的代碼再執(zhí)行一遍污秆。系統(tǒng)為我們提供了一個回調(diào)方法劈猪,當(dāng) light dark 切換時就會觸發(fā)這個方法。
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
// 適配代碼
}
}
題外話
如果你覺得這樣為 CGColor
做適配很麻煩良拼,那么不妨試試 XYColor 這個框架战得。
如何改變當(dāng)前模式
我們可以看到在動圖中是直接改系統(tǒng)的模式,從而讓 App 的模式修改庸推,但是對于某些有夜間模式功能的 App 來說常侦,如果用戶打開了夜間模式浇冰,那么即使現(xiàn)在系統(tǒng)是 light 模式,也要強(qiáng)制用 dark 模式聋亡。
我們可以用以下代碼將當(dāng)前 UIViewController
或 UIView
的模式肘习。
overrideUserInterfaceStyle = .dark
print(traitCollection.userInterfaceStyle) // dark
我們可以看到設(shè)置了 overrideUserInterfaceStyle
之后,traitCollection.userInterfaceStyle
就是我們設(shè)置后的模式了杀捻。
需要給每一個 Controller 和 View 都設(shè)置一遍嗎
答案是不需要井厌,我們先來看一張圖。
當(dāng)我們設(shè)置一個 controller 為 dark 之后致讥,這個 controller 下的 view仅仆,都會是 dark mode,但是后續(xù) present 的 controller 仍然是跟隨系統(tǒng)的樣式垢袱。
因為蘋果對 overrideUserInterfaceStyle
屬性的解釋是這樣的墓拜。
當(dāng)我們在一個普通的 controlle, view 上重寫這個屬性,只會影響當(dāng)前的視圖请契,不會影響前面的 controller 和后續(xù) present 的 controller咳榜。
但是當(dāng)我們在 window
上設(shè)置 overrideUserInterfaceStyle
的時候,就會影響 window
下所有的 controller, view爽锥,包括后續(xù)推出的 controller涌韩。
但是當(dāng)我們在 感謝 hostname 指出錯誤window.rootViewController
上設(shè)置 overrideUserInterfaceStyle
的時候,就會影響 rootViewController
下所有的 controller, view氯夷,包括后續(xù)推出的 controller臣樱。
我們回到剛剛的問題上,如果 App 打開夜間模式腮考,那么很簡單我們只需要設(shè)置 window
的 overrideUserInterfaceStyle
屬性就好了雇毫。
題外話:當(dāng)我們用 Xcode11 創(chuàng)建項目,我們會發(fā)現(xiàn)項目結(jié)構(gòu)發(fā)生了變化踩蔚,window
從 AppDelegate
移到 SceneDelegate
中棚放。
那么如何獲取 SceneDelegate
中的 window
呢,代碼如下
// 這里就簡單介紹一下馅闽,實際項目中飘蚯,如果是iOS應(yīng)用這么寫沒問題,但是對于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)捞蛋,default
和 lightContent
現(xiàn)在 Status Bar
有三種狀態(tài)孝冒,default
, darkContent
和 lightContent
現(xiàn)在的 darkContent
對應(yīng)之前的 default
,現(xiàn)在的 default
會根據(jù)情況自動選擇 darkContent
和 lightContent
UIActivityIndicatorView
之前的 UIActivityIndicatorView
有三種 style
分別為 whiteLarge
, white
和 gray
拟杉,現(xiàn)在全部廢棄庄涡。
增加兩種 style
分別為 medium
和 large
,指示器顏色用 color
屬性修改搬设。
如何在模式切換時打印日志
在 Arguments
中的 Arguments Passed On Launch
里面添加下面這行命令穴店。
-UITraitCollectionChangeLoggingEnabled YES
以上是 iOS 13 如何適配 Dark Mode 的全部內(nèi)容撕捍,如有錯誤歡迎指出。
WWDC鏈接 Implementing Dark Mode on iOS
如果你想知道 iOS 13 還增加了什么新特性可以閱讀這篇文章泣洞。