一种简单的 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

相关文章
相关标签/搜索