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

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

首先我們來看下效果圖


效果圖.gif

如何適配 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.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)在,我們做完了動圖中背景色和文本顏色的適配绳匀,接下來我們看看圖片如何適配

圖片適配

打開 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)在什么樣式梨州。
我們可以在 UIViewControllerUIView 中調(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)前 UIViewControllerUIView 的模式肘习。

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)我們在 window.rootViewController 上設(shè)置 overrideUserInterfaceStyle 的時候,就會影響 rootViewController 下所有的 controller, view氯夷,包括后續(xù)推出的 controller臣樱。 感謝 hostname 指出錯誤

我們回到剛剛的問題上,如果 App 打開夜間模式腮考,那么很簡單我們只需要設(shè)置 windowoverrideUserInterfaceStyle 屬性就好了雇毫。

題外話:當(dāng)我們用 Xcode11 創(chuàng)建項目,我們會發(fā)現(xiàn)項目結(jié)構(gòu)發(fā)生了變化踩蔚,windowAppDelegate 移到 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)捞蛋,defaultlightContent
現(xiàn)在 Status Bar 有三種狀態(tài)孝冒,default, darkContentlightContent
現(xiàn)在的 darkContent 對應(yīng)之前的 default,現(xiàn)在的 default 會根據(jù)情況自動選擇 darkContentlightContent

UIActivityIndicatorView

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

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

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


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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末忧风,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子球凰,更是在濱河造成了極大的恐慌狮腿,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呕诉,死亡現(xiàn)場離奇詭異缘厢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)甩挫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門贴硫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伊者,你說我怎么就攤上這事英遭。” “怎么了亦渗?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵挖诸,是天一觀的道長。 經(jīng)常有香客問我法精,道長税灌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任亿虽,我火速辦了婚禮,結(jié)果婚禮上苞也,老公的妹妹穿的比我還像新娘洛勉。我一直安慰自己,他們只是感情好如迟,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布收毫。 她就那樣靜靜地躺著,像睡著了一般殷勘。 火紅的嫁衣襯著肌膚如雪此再。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天玲销,我揣著相機(jī)與錄音输拇,去河邊找鬼。 笑死贤斜,一個胖子當(dāng)著我的面吹牛策吠,可吹牛的內(nèi)容都是我干的逛裤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼猴抹,長吁一口氣:“原來是場噩夢啊……” “哼带族!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蟀给,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蝙砌,失蹤者是張志新(化名)和其女友劉穎狮含,沒想到半個月后嗤形,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棠枉,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡漠趁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年坑填,在試婚紗的時候發(fā)現(xiàn)自己被綠了你踩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伍俘。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡叽掘,死狀恐怖汁政,靈堂內(nèi)的尸體忽然破棺而出道偷,到底是詐尸還是另有隱情,我是刑警寧澤记劈,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布勺鸦,位于F島的核電站,受9級特大地震影響目木,放射性物質(zhì)發(fā)生泄漏换途。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一刽射、第九天 我趴在偏房一處隱蔽的房頂上張望军拟。 院中可真熱鬧,春花似錦誓禁、人聲如沸懈息。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辫继。三九已至,卻和暖如春俗慈,著一層夾襖步出監(jiān)牢的瞬間姑宽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工闺阱, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留炮车,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像示血,于是被迫代替她去往敵國和親棋傍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容