iOS
在13的版本加入了對深色模式的支持承二,深色模式下App
整體上呈現(xiàn)黑色UI
界面,現(xiàn)在許多App
都完成了深色模式的適配侣诵,但也有少量App
未支持深色模式(這些App
大多是內嵌較多的H5
頁面)例如支付寶等树绩,本文主要介紹iOS
深色模式的適配峦筒。
System Colors
Apple
為了適配深色模式對UIKit
中的UIColor
進行了重新定義氏义,例如將.red
, .blue
和 .yellow
定義為.systemRed
锄列,.systemBlue
和.systemYellow
,這些新定義的System Colors
在深色和淺色模式下表現(xiàn)為不同的顏色惯悠,具體的色值可以見蘋果的官方文檔邻邮。
Semantic Colors
對于一些需要進行文字顯示的控件Apple
也做了深色模式的適配,Apple
新加了Semantic Colors
顏色方案克婶,使用Semantic Colors
時不需要糾結具體的值筒严,只需要在合適的場景使用丹泉,例如當控件是Label
時,在沒有自定義字體顏色時鸭蛙,可以使用.label
類型的的Semantic Colors
摹恨,在淺色模式下顯示黑色字體,在深色模式下顯示白色字體娶视;Semantic Colors
包括.label
晒哄,.separator
,.link
歇万, .systemBackground
和.systemFill
揩晴。
對于系統(tǒng)適配的以上深色模式下的顏色可以使用SemanticUI這個App進行查看勋陪,該App
給出了二種模式下的系統(tǒng)顏色的對比:
Dynamic Colors
當然在實際開發(fā)中很多情況下我們都是需要自定義顏色的贪磺,有幸的是Apple
也給出了相應的方案,那就是通過UIColor.init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)
這個方法進行創(chuàng)建自定義的semantic color
诅愚。
使用代碼的方式:
import UIKit
infix operator |: AdditionPrecedence
public extension UIColor {
static func | (lightMode: UIColor, darkMode: UIColor) -> UIColor {
guard #available(iOS 13.0) else { return lightMode }
return UIColor { (traitCollection) -> UIColor in
return traitCollection.userInterfaceStyle == .light ? lightMode : darkMode
}
}
}
// 使用
let dynamicColor = .black | .white
使用Asset Catalog方式
自動iOS11
開始寒锚,可以在Asset Catalogs
保存自定義顏色,并支持Interface Builder
和code
二種方式使用违孝,自定義的color
目前也支持深色模式刹前。打開Assets.xcassets
,店家左下角的+
號按鈕新增一個Color Set
雌桑,在Any Appearence
(淺色模式)和Dark Appearence
(深色模式)分別添加一種顏色即可喇喉。
當然也支持代碼的使用:
Let view = Uiview()
View.backdroundcolor = Color(named: Color)
Border colors
Border colors
在當主題模式發(fā)生改變時并不會自動的進行適配,所以需要手動的進行處理校坑,可以通過traitCollectionDidChange(_:)
這個方法在進行處理:
import UIKit
extension UIColor {
static let layer = UIColor(light: .init(red: 106 / 255, green: 32 / 255, blue: 119 / 255, alpha: 1),
dark: .init(red: 138 / 255, green: 76 / 255, blue: 146 / 255, alpha: 1))
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
traitCollection.hasDifferentColorAppearance(comparedTo: traitCollection) {
layer.backgroundColor = UIColor.layer.cgColor
}
}
引起
TraitCollection
變化有多種情況的拣技,不只是系統(tǒng)顯示模式的切換,例如當屏幕方向發(fā)生旋轉時也會觸發(fā)上面的方法耍目,所以最好是做一個判斷膏斤,當TraitCollection
的更改影響color
的值時才給layer
賦值,在Xcode
中可以添加一個launch argument:UITraitCollertionChangeLoggingEnabled = YES
來檢測TraitCollection
的改變:
Dynamic Images
圖片資源同樣支持深色模式邪驮,需要使用Assets.xcassets
莫辨,新建一個Assets.xcassets
并在Attributes inspector
點擊Appearances
選擇Any
, Dark
,然后分別為Any Appearances
和Dark Appearances
配置響應的圖片毅访。
實際開發(fā)中圖片資源可能是后臺返回的沮榜,這時候需要使用
image assets
:
import UIKit
public extension UIImageAsset {
convenience init(lightModeImage: UIImage?, darkModeImage: UIImage?) {
self.init()
register(lightModeImage: lightModeImage, darkModeImage: darkModeImage)
}
func register(lightModeImage: UIImage?, darkModeImage: UIImage?) {
register(lightModeImage, for: .light)
register(darkModeImage, for: .dark)
}
func register(_ image: UIImage?, for traitCollection: UITraitCollection) {
guard let image = image else {
return
}
register(image, with: traitCollection)
}
func image() -> UIImage {
if #available(iOS 13.0, tvOS 13.0, *) {
return image(with: .current)
}
return image(with: .light)
}
}
盡量減少圖片
為了適配深色模式,無限的增加圖片資源最終會導致包的大小會增加很多喻粹,為了減少包的體積蟆融,在非必要添加圖片的情況下有以下二種方案:
-
使用tint color
對于那些像toolbars
和tab bars
的圖片,可以用tint color
去渲染這些圖片以滿足深色模式磷斧,首先需要讓圖片以模板的形式渲染:
或者:
let iconImage = UIImage()
let imageView = UIImageView()
imageView.image = iconImage.withRenderingMode(.alwaysTemplate)
使用:
public static var tint: UIColor = {
if #available(iOS 13, *) {
return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in
if UITraitCollection.userInterfaceStyle == .dark {
return Colors.osloGray
} else {
return Colors.dataRock
}
}
} else {
return Colors.dataRock
}
}()
imageView.tintColor = Style.Colors.tint
-
反轉圖片顏色
反轉圖片顏色也是一種好的選擇振愿,只是不是對所有的圖片都合適捷犹,可以使用如下代碼:
extension UIImage {
func invertedColors() -> UIImage? {
guard let ciImage = CIImage(image: self) ?? ciImage, let filter = CIFilter(name: "CIColorInvert") else { return nil }
filter.setValue(ciImage, forKey: kCIInputImageKey)
guard let outputImage = filter.outputImage else { return nil }
return UIImage(ciImage: outputImage)
}
}
自動更新 Dark Mode
現(xiàn)在支持深色模式的App
都會提供淺色和深色模式的選擇,同時支持跟誰系統(tǒng)自動切換冕末,當用戶選擇相應模式后需要用Userdefaults
保存用戶的選擇萍歉。
import UIKit
public extension UserDefaults {
var overridedUserInterfaceStyle: UIUserInterfaceStyle {
get {
UIUserInterfaceStyle(rawValue: integer(forKey: #function)) ?? .unspecified
}
set {
set(newValue.rawValue, forKey: #function)
}
}
}
當保存了用戶選擇后同時需要更新當前App
的顯示:
public extension UIApplication {
func override(_ userInterfaceStyle: UIUserInterfaceStyle) {
// iPad支持多窗口,不支持iPad的話可以刪除這段判斷
if #available(iOS 13.0, *), supportsMultipleScenes {
for connectedScene in connectedScenes {
if let scene = connectedScene as? UIWindowScene {
scene.windows.override(userInterfaceStyle)
}
}
}
else {
windows.override(userInterfaceStyle)
}
}
}
public extension UIWindow {
func override(_ userInterfaceStyle: UIUserInterfaceStyle) {
if #available(iOS 13.0, tvOS 13.0, *) {
overrideUserInterfaceStyle = userInterfaceStyle
}
}
}
public extension Array where Element: UIWindow {
func override(_ userInterfaceStyle: UIUserInterfaceStyle) {
for window in self {
window.override(userInterfaceStyle)
}
}
}
UIApplication.shared.override(style)
UserDefaults.standard.overridedUserInterfaceStyle = style
退出或禁用深色模式
當采用Xcode 11
新建項目時會默認開啟深色模式的档桃,當沒時間或者暫不支持深色模式時可以禁用掉枪孩,簡單的方法時在Info.plist
中添加一個UIUserInterfaceStyle = Light
,也支持為某個View
或者ViewController
單獨設置相應的模式藻肄,但是這種設置不會影響模態(tài)彈出的VC
的模式蔑舞。
let view = UIView()
view.overrideUserInterfaceStyle = .dark
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .dark
}
}
總結:
本文詳細介紹了
iOS
深色模式的適配,最主要的是通過UITraitCollection
來拿到當前的樣式來進行相應的顏色上的適配嘹屯,Apple
也提供的新定義的幾種系統(tǒng)顏色方案并能自動適配深色模式攻询,深色模式適配代碼不多,就是需要將項目所有的界面都要進行適配州弟,工作量巨大钧栖。