Dark Mode在iOS 13中引入,并在WWDC 2019上宣布。它为iOS添加了更暗的主题,并允许您对应用执行相同的操作。这是给用户的绝佳补充,使他们可以在较暗的设计中体验您的应用程序。

在此博客文章中,我们将暗模式支持添加到“ WeTransfer收集”应用程序后,我将与您分享我的经验。

退出并禁用黑暗模式

在我们开始采用Dark界面风格之前,我想简短地告诉您如何选择退出。一旦开始使用Xcode 11构建应用程序,您会注意到默认情况下启用了深色外观。

如果您没有时间添加对深色模式的支持,则只需将其添加UIUserInterfaceStyleInfo.plist并将其设置为即可将其禁用Light

每个视图控制器覆盖黑暗模式

您可以使用以下代码覆盖每个视图控制器的用户界面样式,并将其设置为亮或暗:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        overrideUserInterfaceStyle = .dark
    }
}
按视图覆盖暗模式

您可以对单个UIView实例执行相同的操作:

let view = UIView()
view.overrideUserInterfaceStyle = .dark
每个窗口覆盖黑暗模式

如果要以编程方式禁用暗模式,则可以覆盖每个窗口的用户界面样式:

UIApplication.shared.windows.forEach { window in
    window.overrideUserInterfaceStyle = .dark
}

请注意,我们从此处开始使用windows数组,因为从iOS 13开始不赞成使用keyWindow共享的属性。UIApplication不建议使用它,因为应用程序现在可以支持都具有附加窗口的多个场景。


启用暗模式进行测试

如果您开始在应用中实现更暗的外观,那么拥有一种好的测试方法就很重要。有多种启用和切换外观模式的方法,它们都有其优点。

在模拟器中启用黑暗模式

导航到模拟器上“设置”应用中的“开发人员”页面,然后打开深色外观开关:

swift date如何带T swift dart_黑暗模式

在设备上启用暗模式

在设备上,您可以通过导航到“设置”应用程序中的“显示和亮度”页面来启用暗模式。但是,在开发过程中向控制中心添加选项以在暗和亮模式之间快速切换要容易得多:

swift date如何带T swift dart_黑暗模式_02

从调试菜单切换暗模式

在模拟器打开的情况下在Xcode中工作时,您可能想改用Environment Overrides窗口。这使您可以在调试时快速切换外观:

swift date如何带T swift dart_黑暗模式_03

注意:如果看不到此选项,则可能是在iOS 12或更低版本的设备上运行。

在情节提要中启用黑暗模式

在情节提要板中处理视图时,将情节提要板中的外观设置为深色可能很有用。您可以在底部的设备选择旁边找到此选项:

swift date如何带T swift dart_Dark Mode_04

在视图,视图控制器和窗口中覆盖黑暗模式

上一节介绍了在整个应用程序中启用或禁用Light模式,使用Info.plist或禁用每个视图,视图控制器或窗口。如果您临时想强制将Dark外观用于测试目的,这是一种好方法。


调整深色模式的颜色

在iOS 13的黑暗模式下,苹果还引入了自适应和语义颜色。这些颜色会根据多种影响自动调整,例如是否处于模态演示中。

自适应颜色说明

自适应颜色会自动适应当前外观。自适应颜色会针对不同的界面样式返回不同的值,并且还可能受到诸如样式表形式的表现样式的影响。

语义颜色说明

语义颜色描述了它们的意图,并且也是自适应的。一个示例是label应用于标签的语义颜色。很简单,不是吗?

当您将它们用于预期目的时,它们将针对当前外观正确渲染。该label示例将自动将文本颜色更改为黑色(浅色模式)和白色(深色)。

最好探索所有可用的颜色,并充分利用您真正需要的颜色。

探索自适应和语义颜色

如果可能的话,如果您能够在项目中实现语义和自适应颜色,则采用暗模式会容易得多。为此,我强烈推荐 Aaron Brethorst 开发的 SemanticUI 应用程序,该应用程序可让您查看两种外观中所有可用颜色的概述。

swift date如何带T swift dart_黑暗模式_05

支持iOS 12及更低版本的语义颜色

一旦开始使用语义颜色,您将意识到它们仅支持iOS 13及更高版本。为了解决这个问题,我们可以使用该 UIColor.init(dynamicProvider: @escaping (UITraitCollection) -> UIColor) 方法创建自己的自定义UIColor包装器。这使您可以为iOS 12及更低版本返回不同的颜色。

public enum DefaultStyle {

    public enum Colors {

        public static let label: UIColor = {
            if #available(iOS 13.0, *) {
                return UIColor.label
            } else {
                return .black
            }
        }()
    }
}

public let Style = DefaultStyle.self

let label = UILabel()
label.textColor = Style.Colors.label

这种方法的另一个好处是,您将能够定义自己的自定义Style对象。这样不仅可以主题化,而且在强制使用此新样式配置时,还可以使整个应用程序中的颜色使用更加一致。

创建自定义语义颜色

可以使用前面介绍的 UIColor.init(dynamicProvider: @escaping (UITraitCollection) -> UIColor) 方法创建自定义语义颜色。

通常,您的应用具有自己相同的色泽。可能是这种颜色在“亮”模式下效果很好,而在“暗”模式下效果不佳。为此,您可以基于当前界面样式返回不同的颜色。

public static var tint: UIColor = {
    if #available(iOS 13, *) {
        return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in
            if UITraitCollection.userInterfaceStyle == .dark {
                /// Return the color for Dark Mode
                return Colors.osloGray
            } else {
                /// Return the color for Light Mode
                return Colors.dataRock
            }
        }
    } else {
        /// Return a fallback color for iOS 12 and lower.
        return Colors.dataRock
    }
}()

可以通过使用 userInterfaceStyle 当前特征收集上的属性来检测暗模式。设置为时,dark您会知道当前外观设置为深色。

对于深色模式,边框颜色不会动态更新

当您将自适应颜色与CALayers结合使用时,您会注意到在应用中实时切换外观时这些颜色不会更新。您可以通过使用traitCollectionDidChange(_:)方法解决此问题。

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    /// Border color is not automatically catched by trait collection changes. Therefore, update it here.
    layer.borderColor = Style.Colors.separator.cgColor
}

为暗模式更新资产和图像

完成所有颜色的更新后,就可以更新应用程序中的资产了。

最简单的方法是使用图像资产目录。您可以为每个外观添加额外的图像。

swift date如何带T swift dart_swift date如何带T_06


这也使您的图像具有自适应性,并针对当前界面样式相应地调整图像。

为图像和图标应用色彩

为每种外观添加额外的资产并不总是最好的选择。最后,它使您的应用程序更大。

因此,一个很好的选择是寻找可以使用色彩的图像。这尤其适用于工具栏和选项卡栏中使用的图标。

首先,您需要使资产呈现为模板:

swift date如何带T swift dart_swift date如何带T_07


您可以在代码中执行相同的操作:

let iconImage = UIImage()
let imageView = UIImageView()
imageView.image = iconImage.withRenderingMode(.alwaysTemplate)

之后,您可以简单地设置图像视图的色调颜色,以使图标根据当前外观调整其颜色:

imageView.tintColor = Style.Colors.tint
反转颜色作为图像解决方案

反转颜色可以是节省应用程序大小的另一种方法。这并不总是适用于每个图像,但是可以作为一种解决方案,阻止您将其他资产添加到捆绑中。

您可以通过使用以下 UIImage 扩展名来做到这一点:

extension UIImage {
    /// Inverts the colors from the current image. Black turns white, white turns black etc.
    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)
    }
}

当外观更新时,您需要手动更新图像。因此,建议使用以下方法:

// MARK: - Dark Mode Support
private func updateImageForCurrentTraitCollection() {
    if traitCollection.userInterfaceStyle == .dark {
        imageView.image = originalImage?.invertedColors()
    } else {
        imageView.image = originalImage
    }
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    updateImageForCurrentTraitCollection()
}

结论

我们介绍了许多在您的应用中适应黑暗模式的技巧。我们还解释了使用语义和自适应颜色的好处。希望这可以帮助您更有效地实施暗模式!

谢谢!