美文网首页OC-开发案例收集iOS Developer
iOS开发高级分享 - 兼容暗模式

iOS开发高级分享 - 兼容暗模式

作者: iOS_小久 | 来源:发表于2019-10-29 22:43 被阅读0次

    苹果今年早些时候宣布在iOS上使用“暗模式”,该模式为用户提供了选择系统范围内的浅色或深色外观的选项。它从iOS 13开始可用。但是,如果您对iOS的采用率不满意,仍然需要支持旧版本的iOS。在本文中,我们来看看如何介绍适用于所有iOS版本(包括iOS 13)的黑暗模式。

    先决条件

    应该指出的是,采用暗模式(或暗主题)并不是一件容易的事,因为乍一看。因此,在继续前进之前,需要进行一些准备。在UI代码中引入一些结构并定义应用程序范围的调色板,字体,UI元素样式等是有意义的。

    我建议您观看WWDC 上有关在iOS上实现暗模式的话题并查看文档以概述此功能以及UIKit中引入的相关更改。NSHipster上还发布了有关如何使您的应用程序为黑暗模式做好准备的很好的指南。

    iOS 13之前的黑暗模式

    我们应该做的第一件事就是声明一个主题类型。它定义了一种可以应用于整个应用程序的样式,并指定了每个视图的外观细节。该Theme结构包含一个嵌套的Type枚举和两个存储主题类型和调色板的属性:

    struct Theme {
        enum `Type` {
            case light
            case dark
        }
        let type: Type
        let colors: ColorPalette
    }
    
    

    假设我们在项目中定义了一个调色板。然后,我们可以创建明暗主题:

    extension Theme {
        static let light = Theme(type: .light, colors: .light)
        static let dark = Theme(type: .dark, colors: .dark)
    }
    
    

    为了能够使用当前主题更新我们的UI组件,应该有一个所有这些协议都遵循的协议。这可以通过以下Themeable协议来实现:

    protocol Themeable: class {
        func apply(theme: Theme)
    }
    
    

    接下来,我们将需要一种跟踪当前应用程序主题并提供订阅当前主题更改的方法。为此,我们将声明一个ThemeProvider类,该类将当前主题存储在中UserDefaults,保留主题观察者NSHashTable并在当前主题发生更改时通知它们:

    class ThemeProvider {
        static let shared = ThemeProvider()
        var theme: Theme {
            didSet {
                UserDefaults.standard.set(theme == .dark, forKey: "isDark")
                notifyObservers()
            }
        }
        private var observers: NSHashTable<AnyObject> = NSHashTable.weakObjects()
    
        private init() {
            self.theme = UserDefaults.standard.bool(forKey: "isDark") ? .dark : .light
        }
    
        func toggleTheme() {
            theme = theme == .light ? .dark : .light
        }
    
        func register<Observer: Themeable>(observer: Observer) {
            observer.apply(theme: theme)
            self.observers.add(observer)
        }
    
        private func notifyObservers() {
            DispatchQueue.main.async {
                self.observers.allObjects
                    .compactMap({ $0 as? Themeable })
                    .forEach({ $0.apply(theme: self.theme) })
            }
        }
    }
    
    

    有了上面提到的代码,我们现在可以更新我们的应用程序并使其在黑暗和明亮模式下都能工作。我们可以从更新所有UI组件开始,例如视图控制器,单元格,控件等,以符合Themeable协议并在apply(theme:)方法中更新其外观。要收听主题更改,我们应该通过register(observer:)ThemeProvider类调用方法来注册观察者。可以为视图控制器完成以下操作:

    class MoviesTableViewController: UITableViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            themeProvider.register(observer: self)
        }
    }
    
    extension MoviesTableViewController: Themeable {
        func apply(theme: Theme) {
            // update UI attributes
        }
    }
    
    

    除此之外,还应该有一个设置屏幕,用户可以在其中打开和关闭暗模式。使用Gagat库甚至可以更好地完成此操作,并可以使用两指平底锅以交互方式在iOS应用程序中的两个不同主题之间进行切换。最后,我们应该获得对iOS 12上运行的应用程序的暗模式支持。

    [图片上传中...(image-afc152-1572356356693-1)]

    在iOS 13中采用黑暗模式

    在iOS 13中,Apple提供了系统范围的暗模式支持。现在,我们可以在资产目录中创建颜色和图像,并为暗模式指定其他外观。最终,当用户从“设置”打开暗模式时,可以自动更新应用程序的UI。在某些情况下,您可能想检测外观变化并相应地更新UI。这可以通过traitCollectionDidChange(_:)在任何符合UITraitEnvironment协议的UIKit类中实现方法来实现。考虑到所有这些因素,我们应该在当前解决方案中进行一些调整。

    利用动态颜色,我们可以定义自适应调色板和主题:

    struct Theme {
        static let light = Theme(type: .light, colors: .light)
        static let dark = Theme(type: .dark, colors: .dark)
        @available(iOS 13.0, *)
        static let adaptive = Theme(type: .adaptive, colors: .adaptive)
    
        enum `Type` {
            case light
            case dark
            @available(iOS 13.0, *)
            case adaptive
        }
    }
    
    

    ThemeProvider我们之前描述不会在iOS 13.运行我们可以宣布一个新的应用程序很好地工作DefaultThemeProvider类,并让两个提供符合ThemeProvider协议:

    protocol ThemeProvider: class {
        var theme: Theme { get }
        func register<Observer: Themeable>(observer: Observer)
        func toggleTheme()
    }
    
    @available(iOS 13.0, *)
    public class DefaultThemeProvider: NSObject, ThemeProvider {
        static let shared = DefaultThemeProvider()
        let theme: Theme = .adaptive
    
        private override init() {
            super.init()
        }
    
        func register<Observer: Themeable>(observer: Observer) {
            observer.apply(theme: theme)
        }
    
        func toggleTheme() {
            assertionFailure("The function \(DefaultThemeProvider.self).\(#function) shouldn't be used!")
        }
    }
    
    

    接下来,我们需要一种ThemeProvider基于当前iOS版本选择实现的方法:

    extension Themeable where Self: UITraitEnvironment {
        var themeProvider: ThemeProvider {
            if #available(iOS 13.0, *) {
                return DefaultThemeProvider.shared
            } else {
                return LegacyThemeProvider.shared
            }
        }
    }
    
    

    实施该功能后,我们应该获得对iOS 13上运行的应用程序的暗模式支持。

    苹果今年早些时候宣布在iOS上使用“暗模式”,该模式为用户提供了选择系统范围内的浅色或深色外观的选项。它从iOS 13开始可用。但是,如果您对iOS的采用率不满意,仍然需要支持旧版本的iOS。在本文中,我们来看看如何介绍适用于所有iOS版本(包括iOS 13)的黑暗模式。

    先决条件

    应该指出的是,采用暗模式(或暗主题)并不是一件容易的事,因为乍一看。因此,在继续前进之前,需要进行一些准备。在UI代码中引入一些结构并定义应用程序范围的调色板,字体,UI元素样式等是有意义的。

    我建议您观看WWDC 上有关在iOS上实现暗模式的话题并查看文档以概述此功能以及UIKit中引入的相关更改。NSHipster上还发布了有关如何使您的应用程序为黑暗模式做好准备的很好的指南。

    iOS 13之前的黑暗模式

    我们应该做的第一件事就是声明一个主题类型。它定义了一种可以应用于整个应用程序的样式,并指定了每个视图的外观细节。该Theme结构包含一个嵌套的Type枚举和两个存储主题类型和调色板的属性:

    struct Theme {
        enum `Type` {
            case light
            case dark
        }
        let type: Type
        let colors: ColorPalette
    }
    
    

    假设我们在项目中定义了一个调色板。然后,我们可以创建明暗主题:

    extension Theme {
        static let light = Theme(type: .light, colors: .light)
        static let dark = Theme(type: .dark, colors: .dark)
    }
    
    

    为了能够使用当前主题更新我们的UI组件,应该有一个所有这些协议都遵循的协议。这可以通过以下Themeable协议来实现:

    protocol Themeable: class {
        func apply(theme: Theme)
    }
    
    

    接下来,我们将需要一种跟踪当前应用程序主题并提供订阅当前主题更改的方法。为此,我们将声明一个ThemeProvider类,该类将当前主题存储在中UserDefaults,保留主题观察者NSHashTable并在当前主题发生更改时通知它们:

    class ThemeProvider {
        static let shared = ThemeProvider()
        var theme: Theme {
            didSet {
                UserDefaults.standard.set(theme == .dark, forKey: "isDark")
                notifyObservers()
            }
        }
        private var observers: NSHashTable<AnyObject> = NSHashTable.weakObjects()
    
        private init() {
            self.theme = UserDefaults.standard.bool(forKey: "isDark") ? .dark : .light
        }
    
        func toggleTheme() {
            theme = theme == .light ? .dark : .light
        }
    
        func register<Observer: Themeable>(observer: Observer) {
            observer.apply(theme: theme)
            self.observers.add(observer)
        }
    
        private func notifyObservers() {
            DispatchQueue.main.async {
                self.observers.allObjects
                    .compactMap({ $0 as? Themeable })
                    .forEach({ $0.apply(theme: self.theme) })
            }
        }
    }
    
    

    有了上面提到的代码,我们现在可以更新我们的应用程序并使其在黑暗和明亮模式下都能工作。我们可以从更新所有UI组件开始,例如视图控制器,单元格,控件等,以符合Themeable协议并在apply(theme:)方法中更新其外观。要收听主题更改,我们应该通过register(observer:)ThemeProvider类调用方法来注册观察者。可以为视图控制器完成以下操作:

    class MoviesTableViewController: UITableViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            themeProvider.register(observer: self)
        }
    }
    
    extension MoviesTableViewController: Themeable {
        func apply(theme: Theme) {
            // update UI attributes
        }
    }
    
    

    除此之外,还应该有一个设置屏幕,用户可以在其中打开和关闭暗模式。使用Gagat库甚至可以更好地完成此操作,并可以使用两指平底锅以交互方式在iOS应用程序中的两个不同主题之间进行切换。最后,我们应该获得对iOS 12上运行的应用程序的暗模式支持。

    在iOS 13中采用黑暗模式

    在iOS 13中,Apple提供了系统范围的暗模式支持。现在,我们可以在资产目录中创建颜色和图像,并为暗模式指定其他外观。最终,当用户从“设置”打开暗模式时,可以自动更新应用程序的UI。在某些情况下,您可能想检测外观变化并相应地更新UI。这可以通过traitCollectionDidChange(_:)在任何符合UITraitEnvironment协议的UIKit类中实现方法来实现。考虑到所有这些因素,我们应该在当前解决方案中进行一些调整。

    利用动态颜色,我们可以定义自适应调色板和主题:

    struct Theme {
        static let light = Theme(type: .light, colors: .light)
        static let dark = Theme(type: .dark, colors: .dark)
        @available(iOS 13.0, *)
        static let adaptive = Theme(type: .adaptive, colors: .adaptive)
    
        enum `Type` {
            case light
            case dark
            @available(iOS 13.0, *)
            case adaptive
        }
    }
    
    

    ThemeProvider我们之前描述不会在iOS 13.运行我们可以宣布一个新的应用程序很好地工作DefaultThemeProvider类,并让两个提供符合ThemeProvider协议:

    protocol ThemeProvider: class {
        var theme: Theme { get }
        func register<Observer: Themeable>(observer: Observer)
        func toggleTheme()
    }
    
    @available(iOS 13.0, *)
    public class DefaultThemeProvider: NSObject, ThemeProvider {
        static let shared = DefaultThemeProvider()
        let theme: Theme = .adaptive
    
        private override init() {
            super.init()
        }
    
        func register<Observer: Themeable>(observer: Observer) {
            observer.apply(theme: theme)
        }
    
        func toggleTheme() {
            assertionFailure("The function \(DefaultThemeProvider.self).\(#function) shouldn't be used!")
        }
    }
    
    

    接下来,我们需要一种ThemeProvider基于当前iOS版本选择实现的方法:

    extension Themeable where Self: UITraitEnvironment {
        var themeProvider: ThemeProvider {
            if #available(iOS 13.0, *) {
                return DefaultThemeProvider.shared
            } else {
                return LegacyThemeProvider.shared
            }
        }
    }
    
    

    实施该功能后,我们应该获得对iOS 13上运行的应用程序的暗模式支持。

    结论

    在本文中,我们探索了如何通过实现可在包括iOS 13在内的所有iOS版本上使用的深色模式来使您的应用看起来更漂亮。我们在此描述的解决方案可以轻松扩展。您可以在Github上找到演示项目的源代码。如果您有任何疑问,建议或反馈,请随时随地加入交流群1012951431选择加入一起交流,一起学习。期待你的加入!(进群可领取学习礼包)

    https://www.onswiftwings.com/posts/dark-mode/

    相关文章

      网友评论

        本文标题:iOS开发高级分享 - 兼容暗模式

        本文链接:https://www.haomeiwen.com/subject/awqnvctx.html