
在
macOS 10.14中,蘋果在系統(tǒng)本身樣式(Light (aqua) appearance
)基礎(chǔ)上推出了暗黑模式(dark appearance),這種模式下可以更突出顯示應(yīng)用窗口中的內(nèi)容,讓用戶的關(guān)注焦點(diǎn)聚集在App本身的視圖中以便獲取更佳的視覺(jué)體驗(yàn).關(guān)于AppKit中的系統(tǒng)視圖,蘋果默認(rèn)已經(jīng)進(jìn)行了暗黑模式適配升級(jí),但對(duì)于許多自定義的View,還是需要我們花一點(diǎn)點(diǎn)時(shí)間處理的.
0x00: 關(guān)于 NSAppearance
在macOS 10.9+ 的時(shí)候,蘋果就提供了NSAppearance這個(gè)類來(lái)協(xié)助AppKit管理App的UI控件. NSAppearance決定著AppKit如何渲染每個(gè)UI控件的效果,尤其是與顏色或者圖片相關(guān)的部分.
- App啟動(dòng)后會(huì)獲取系統(tǒng)當(dāng)前樣式(
Light Appearance或者Dark Appearance). -
NSWindow會(huì)繼承App的appearance效果; -
NSView會(huì)繼承其父類或者NSWindow的appearance效果; - 開發(fā)者可以設(shè)置App的
整體或者部分的appearance效果; - 當(dāng)
Appkit繪制UI控件時(shí),會(huì)自動(dòng)將當(dāng)前的appearance賦值給控件的appearance(在當(dāng)前線程中進(jìn)行); -
NSAppearance會(huì)影響系統(tǒng)字體(font),顏色(color),文本(text),圖片(image)的相關(guān)繪制路徑(draw path)進(jìn)而影響顯示效果.
0x01: 顏色適配(NSColor)
當(dāng)用戶切換
Light / Dark Appearance時(shí),UI控件的顏色有著明顯不同的效果.在macOS 10.14之前我們對(duì)于一個(gè)控件的顏色值經(jīng)常使用硬編碼方式,因此當(dāng)appearance變化時(shí),這些硬編碼的色值就難以適應(yīng)了.
當(dāng)Appearance變化時(shí),關(guān)于NSColor的適配蘋果官方給出兩種簡(jiǎn)單并且易于實(shí)現(xiàn)的方案:
-
使用帶有語(yǔ)義的Color:
那么問(wèn)題來(lái)了,到底什么是帶有語(yǔ)義的Color呢? 看一下蘋果官方的原文:
Semantic colors let you specify colors based on their intended usage, rather than on the actual color.
簡(jiǎn)單的說(shuō)就是根據(jù)使用場(chǎng)景來(lái)描述顏色,而不使用確切的值來(lái)描述顏色.
我們以一個(gè)Label 的例子來(lái)看一下代碼與效果:
設(shè)置labelColor
運(yùn)行效果:

系統(tǒng)提供的語(yǔ)義Color可以參考蘋果開發(fā)者文檔中的:UI Element Colors
例如Label相關(guān)的有:labelColor , secondaryLabelColor,tertiaryLabelColor,quaternaryLabelColor等.
除了這些語(yǔ)義Color之外,系統(tǒng)還提供了一下可適配的Color,通常都以system+顏色方式命名.例子如下:
NSColor.systemRed
NSColor.systemBlue
NSColor.systemGray
NSColor.systemPink
-
使用Assets Color:(推薦)
更多時(shí)候我們希望能夠有更多自己可以定義的顏色,這時(shí)系統(tǒng)提供的語(yǔ)義Color就會(huì)顯得不夠用,這時(shí)我們可以使用Assets Color,具體操作請(qǐng)參考下圖示例:
Assets Color 設(shè)置
Appearance 說(shuō)明
代碼調(diào)用Assets Color:"Color"是在Assets 中創(chuàng)建的顏色名稱
調(diào)用Assets Color
運(yùn)行效果:
Assets Color 運(yùn)行效果
0x02: 圖片適配(NSImage)
在
App中圖片是非常重要的UI資源,為了在合適的Appearance下顯示正確的圖片,主要有下面的三種方式.
-
Image Assets
使用Assets Image與Assets Color非常相似,具體請(qǐng)參考操作圖例:
Assets Image Set
Assets Image 的適配場(chǎng)景(即當(dāng)下面場(chǎng)景變化時(shí),會(huì)Appkit會(huì)自動(dòng)調(diào)整Image進(jìn)行適配):
- Screen resolution(
屏幕分辨率):
Appkit會(huì)自動(dòng)根據(jù)當(dāng)前屏幕的解析度選取最佳的image進(jìn)行顯示 - Light and dark appearances (
Appearance切換):
Any Appearance中的圖片會(huì)適配macOS全版本,Light和Dark僅適用macOS10.14之后的版本 - High contrast (
高對(duì)比度):
使圖片與周邊的內(nèi)容對(duì)比根據(jù)突出,僅能用于macOS10.14+之后的版本
-
Template Images
使用模版圖片也是一種常用的適配解決方案,典型的案例就是設(shè)置控件的icon(比如一個(gè)播放或者暫停的按鈕).這種方法需要配合使用圖片編輯軟件(項(xiàng)目中的話通常就是UI設(shè)計(jì)師來(lái)處理)制作圖片模版,具體使用僅需兩個(gè)步驟即可:
- UI設(shè)計(jì)師需要根據(jù)場(chǎng)景設(shè)計(jì)圖片,但需要遵守如下規(guī)則:
template 設(shè)置規(guī)則
需要忽略的部分使用透明背景
需要顯示的部分使用黑色或者部分透明的黑色 -
設(shè)置圖片的渲染模式為
Template:
設(shè)置圖片渲染模式
-
Drawing Handler
使用NSImage的init(size:flipped:drawingHandler:)方法可以讓Appkit根據(jù)appearance變化時(shí)自動(dòng)調(diào)用drawingHandler中的代碼進(jìn)行圖片創(chuàng)建,從而實(shí)現(xiàn)適配效果;
0x03: 自定義View 適配(NSView)
當(dāng)改變當(dāng)前的appearance時(shí),AppKit會(huì)自動(dòng)調(diào)用NSView的下面幾個(gè)方法(根據(jù)情況調(diào)用)
- updateLayer()
- draw(_:)
- layout()
- updateConstraints()
這樣我們就有機(jī)會(huì)在變更appearance時(shí),通過(guò)重載上面的方法來(lái)實(shí)現(xiàn)自定義view的UI適配工作,示例代碼如下:
override func updateLayer() {
self.layer?.backgroundColor = NSColor.textBackgroundColor.cgColor
// Other updates.
}
注意點(diǎn)!!!
NSColor會(huì)立刻生效,但CGColor需要App再次啟動(dòng)才會(huì)生效!
0x04: 定制App的appearance(NSApp)
- 設(shè)置
NSView或者NSWindow的appearance:
NSView Appearance
注意點(diǎn)!!!
Appearance是存在繼承關(guān)系的:NSApp->NSWindow->NSView - 通過(guò)代碼方式設(shè)置
NSView的appearance:
class MyContentView : NSView {
func adoptAquaAppearance()
self.appearance = NSAppearance(named: .aqua)
}
}
- 設(shè)置
NSApp的Appearance:
NSApp.appearance = NSAppearance(named: .darkAqua)
0x05: Visual Effect View
關(guān)于NSVisualEffectView的Appearance適配,蘋果官方建議采用根據(jù)使用明確場(chǎng)景語(yǔ)義枚舉.例如在一個(gè)popOver的窗口中,推薦使用NSVisualEffectView.Material.popover,這樣系統(tǒng)就根據(jù)appearance變化自動(dòng)選擇合適的效果了.同時(shí)系統(tǒng)也廢棄了如下的枚舉:
- NSVisualEffectView.Material.light
Deprecated - NSVisualEffectView.Material.dark
Deprecated - NSVisualEffectView.Material.mediumLight
Deprecated - NSVisualEffectView.Material.ultraDark
Deprecated
0x06: 當(dāng)appearance 切換時(shí),應(yīng)避免耗時(shí)操作
當(dāng)切換系統(tǒng)的Appearance時(shí),AppKit會(huì)同時(shí)更新UI控件,這部分工作通常都是自動(dòng)完成的.但有時(shí)也會(huì)調(diào)用開發(fā)者編寫的代碼,例如你使用了NSImage的draw handler 方式創(chuàng)建圖片對(duì)象,又或者使用了KVO監(jiān)聽一個(gè)視圖或者窗口的effectiveAppearance屬性,因此請(qǐng)需要注意下面幾點(diǎn):
- 盡可能快的更新UI;
- 不要執(zhí)行與appearance變更無(wú)關(guān)的任務(wù);
- appearance變化時(shí)AppKit會(huì)自動(dòng)添加過(guò)渡效果動(dòng)畫,但如果你的更新UI代碼任務(wù)過(guò)重,AppKit將會(huì)丟棄過(guò)渡效果動(dòng)畫!
0x07: one more thing
為了考慮兼容macOS10.14之前的App版本,但又想支持Dark Appearance的效果,那么可以在Info.plist中添加 NSRequiresAquaSystemAppearancekey,并設(shè)置值為true即可.
這樣做的前提是要保證App在macOS10.14的Dark Mode下可以正常適配UI效果~.








