iOS適配深色DarkMode模式

iOS在13的版本加入了對深色模式的支持,深色模式下App整體上呈現黑色UI界面,現在許多App都完成了深色模式的適配,但也有少量App未支持深色模式(這些App大多是內嵌較多的H5頁面)例如支付寶等,本文主要介紹iOS深色模式的適配。

System Colors

Apple為了適配深色模式對UIKit中的UIColor進行了重新定義,例如將.red, .blue.yellow定義為.systemRed,.systemBlue.systemYellow,這些新定義的System Colors在深色和淺色模式下表現為不同的顏色,具體的色值可以見蘋果的官方文檔

Semantic Colors

對于一些需要進行文字顯示的控件Apple也做了深色模式的適配,Apple新加了Semantic Colors顏色方案,使用Semantic Colors時不需要糾結具體的值,只需要在合適的場景使用,例如當控件是Label時,在沒有自定義字體顏色時,可以使用.label類型的的Semantic Colors,在淺色模式下顯示黑色字體,在深色模式下顯示白色字體;Semantic Colors包括.label.separator.link.systemBackground.systemFill

對于系統適配的以上深色模式下的顏色可以使用SemanticUI這個App進行查看,該App給出了二種模式下的系統顏色的對比:

Dynamic Colors

當然在實際開發中很多情況下我們都是需要自定義顏色的,有幸的是Apple也給出了相應的方案,那就是通過UIColor.init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)這個方法進行創建自定義的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 Buildercode二種方式使用,自定義的color目前也支持深色模式。打開Assets.xcassets ,店家左下角的+號按鈕新增一個Color Set,在Any Appearence(淺色模式)和Dark Appearence(深色模式)分別添加一種顏色即可。


當然也支持代碼的使用:

Let view =  Uiview()
View.backdroundcolor = Color(named: Color)

Border colors

Border colors在當主題模式發生改變時并不會自動的進行適配,所以需要手動的進行處理,可以通過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變化有多種情況的,不只是系統顯示模式的切換,例如當屏幕方向發生旋轉時也會觸發上面的方法,所以最好是做一個判斷,當TraitCollection的更改影響color的值時才給layer賦值,在Xcode中可以添加一個launch argument:UITraitCollertionChangeLoggingEnabled = YES來檢測TraitCollection的改變:

Dynamic Images

圖片資源同樣支持深色模式,需要使用Assets.xcassets,新建一個Assets.xcassets并在Attributes inspector點擊Appearances選擇Any, Dark,然后分別為Any AppearancesDark Appearances配置響應的圖片。


實際開發中圖片資源可能是后臺返回的,這時候需要使用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
    對于那些像toolbarstab 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

現在支持深色模式的App都會提供淺色和深色模式的選擇,同時支持跟誰系統自動切換,當用戶選擇相應模式后需要用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單獨設置相應的模式,但是這種設置不會影響模態彈出的VC的模式。

let view = UIView()
view.overrideUserInterfaceStyle = .dark
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        overrideUserInterfaceStyle = .dark
    }
}

總結:

本文詳細介紹了iOS深色模式的適配,最主要的是通過UITraitCollection來拿到當前的樣式來進行相應的顏色上的適配,Apple也提供的新定義的幾種系統顏色方案并能自動適配深色模式,深色模式適配代碼不多,就是需要將項目所有的界面都要進行適配,工作量巨大。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容