一種簡單的 iOS 暗黑模式實現方案(支持低版本)

說說最近對於 iOS 系統黑暗主題適配(兼容iOS 13 如下版本)的方案研究.git

himg

iOS 13 開始 Apple 在系統層面支持了黑暗模式, 如今不少 App 也都支持了黑暗模式. 也有關於黑暗模式的不少成熟的開源實現方案, 按道理我沒有必要再去本身實現一套了. 可是在調查了相關方案實現後我發現仍是有一種更輕量, 代碼侵入更小, 更符合 Apple 風格並且學習及遷移成本都很小的方案.github

方案概述: 原生 + 置換 window 的 rootViewController

這套方案的核心思想是很是簡單的:swift

  • 利用對系統版本的判斷來生成一個顏色
    • iOS 13 及以上返回系統支持的動態顏色 init(dynamicProvider: @escaping (UITraitCollection) -> UIColor),
    • 其餘低版本系統則使用根據用戶所設置的主題所對應的顏色.
  • 在切換的時候也會判斷版本
    • iOS 13 及以上版本直接設置 windowoverrideUserInterfaceStyle
    • 其餘低版本整個初始化 VC 而後將 VC 設置爲 windowrootViewController

具體代碼實現以下api

設置可切換的主題

在這一步咱們設置須要支持的主題數量markdown

enum Theme: Int, CaseIterable {
    case none = 0
    case light = 1
    case dark = 2

    var title: String {
        switch self {
            case .none: return "Follow"
            case .light: return "Light"
            case .dark: return "Dark"
        }
    }

    @available(iOS 13.0, *)
    var mode: UIUserInterfaceStyle {
        switch self {
            case .none: return .unspecified
            case .light: return .light
            case .dark: return .dark
        }
    }
}
複製代碼

設置顏色/圖片

class Tools {
    @UserDefaultStorage(keyName: "appTheme")
    static var _style: Int? // 此處用於全局存儲 UserDefaults 屬性

    static var style: Theme {
        get { return Theme(rawValue: (_style ?? 0)) ?? .dark }
        set { _style = newValue.rawValue }
    }

    /// 創造顏色, 核心方法
    static func makeColor(light: UIColor, dark: UIColor) -> UIColor {
        if #available(iOS 13.0, *) {
            return UIColor { $0.userInterfaceStyle == .light ? light : dark }
        } else {
            return Tools.style == .light ? light : dark
        }
    }

    /// 創造 img, 核心方法
    static func makeImage(light: UIImage, dark: UIImage) -> UIImage {
        if #available(iOS 13.0, *) {
            let image = UIImage()
            image.imageAsset?.register(light, with: .init(userInterfaceStyle: .light))
            image.imageAsset?.register(dark, with: .init(userInterfaceStyle: .dark))
            return image
        } else {
            return Tools.style == .light ? light : dark
        }
    }
複製代碼

切換主題

func changeTheme(theme: Theme) {
    Tools.style = theme
    guard let window = UIWindow.hl.getKeyWindow() else { return }
    if #available(iOS 13.0, *) {
        window.overrideUserInterfaceStyle = theme.mode
    } else {
        guard let rootVC = window.rootViewController else { return }
        let tabbar = Tools.setTabVC(withIndex: self.index)
        window.rootViewController = tabbar
    }
}
複製代碼

系統啓動時檢查全局設置的 style 屬性

不少產品的要求是能夠自由切換黑暗與白天模式, 在使用自定義模式的時候就不跟隨系統了, 所以若是有這樣的需求的話就須要在啓動時進行判斷並設置app

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
                 -> Bool {

    window = UIWindow(frame: UIScreen.main.bounds)

    // 若是是 iOS
    if #available(iOS 13.0, *) {
        window?.overrideUserInterfaceStyle = Tools.style.mode
    }

    window?.rootViewController = Tools.getTabVC(withIndex: 0)
    window?.makeKeyAndVisible()

    return true
}
複製代碼

其餘主流方案

在主題調研的這幾天內也看了一些開源實現的第三方庫, 說說對於這幾個庫的見解吧, 也可讓之後人少走點彎路ide

RxTheme

  • 屬於 Rx 社區的一個產品, 其方案的實現基於 RxSwift.
  • 實現的方式是經過 Rx 的綁定創建關係, 每一個 view 的顏色的綁定關係有該 view 進行持有, 在收到信號後進行改變主題顏色
  • 由於其實現是基於協議, 所以若是是組件化開發的話必須把全部顏色所有集中管理在一個組件中, 其餘組件很難進行顏色的擴展添加.
  • 該庫缺乏不少屬性的擴展, 若是碰到一個本身須要的且剛好該庫沒有的屬性時須要進行相應二次擴展

SwiftTheme

SwiftTheme 是一個很經典的 Swift 語言寫的主題方案了.oop

  • 屬性擴展較全面, 不多有屬性是該庫沒有考慮到的
  • 實現的方式是對 NSObject 擴展出一個 Dictionary 存儲屬性, 將全部的設置過的控件存入其中, 而後在用戶觸發開關後以 Notification 的方式進行 notify, 收到 Notification 後對Dictionary 進行遍歷, 對其中的每一個控件的每一個屬性進行判斷而後從新賦值
  • 由於其實現方案基於通知, 在 view 數量級較多狀況下性能可能會存在問題

本方案總結

基於對上面幾種開源主流方案的對比, 本方案有着如下的優缺點:組件化

  • 優勢性能

    • 代碼侵入(改動成本)小
    • 性能無壓力
    • 無第三方庫依賴
    • 之後能夠平滑切換到 iOS 13
    • 系統級動畫
  • 缺點

    • 只支持黑暗與明亮模式(也能夠支持到其餘主題, 可是那樣的話即便是 iOS 13 以上設備也須要使用置換 rootViewController 的方案了)
    • 全部涉及到 CGColor 的 view 須要實現 traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) 方法
    • iOS 12 及如下系統切換時須要重置 rootViewController, 所以全部 vc 都會被釋放掉再重建, 即若是其餘頁面有正在操做的邏輯, 那麼就會丟失現場

總的來講, 本方案至關於提供了一種實現主題的精簡型方案.

Demo

Talk is cheap, show me the code!

基於本文思路的實現 Demo: github.com/HanleyLee/D…

最後

本文做者 Hanley Lee, 首發於 閃耀旅途, 若是對本文比較承認, 歡迎 Follow

相關文章
相關標籤/搜索