UIColor return 深色模式颜色的错误值

UIColor return wrong values for dark mode colors

我有一个自定义的 UITextField 子类,在其中键入内容时会更改其边框颜色。我正在通过调用

来监听变化
self.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)

然后,在 textFieldDidChange(_:) 我正在做:

self.layer.borderColor = UIColor(named: "testColor")?.cgColor

其中 testColor 是在 Assets.xcassets 中定义的颜色,具有浅色和深色模式的变体。问题是 UIColor(named: "testColor")?.cgColor 似乎总是 return 灯光模式的颜色。

这是 iOS 13 beta 中的错误还是我做错了什么?有 a GitHub repo 代码表现出这种行为。 运行 项目,从 XCode 切换到深色模式,然后开始在文本字段中输入内容。

简答

在这种情况下,您需要指定使用哪个特征集合来解析动态颜色。

self.traitCollection.performAsCurrent {
    self.layer.borderColor = UIColor(named: "testColor")?.cgColor
}

self.layer.borderColor = UIColor(named: "testColor")?.resolvedColor(with: self.traitCollection).cgColor

更长的答案

当您在动态 UIColor 上调用 cgColor 方法时,它需要解析动态颜色的值。这是通过引用当前特征集合 UITraitCollection.current 来完成的。

当前特征集合由 UIKit 在调用您对某些方法的覆盖时设置,特别是:

  • UIView
    • 绘图()
    • layoutSubviews()
    • traitCollectionDidChange()
    • tintColorDidChange()
  • UIViewController
    • viewWillLayoutSubviews()
    • viewDidLayoutSubviews()
    • traitCollectionDidChange()
  • UIPresentationController
    • containerViewWillLayoutSubviews()
    • containerViewDidLayoutSubviews()
    • traitCollectionDidChange()

但是,除了重写这些方法之外,当前特征集合不一定设置为任何特定值。因此,如果您的代码未覆盖其中一种方法,并且您想要解析动态颜色,则您有责任告诉我们要使用的特征集合。

(这是因为可以覆盖任何视图或视图控制器的 userInterfaceStyle 特性,因此即使设备可能设置为亮模式,您也可能有一个处于暗模式的视图。)

您可以使用 UIColor 方法 resolvedColor(with:). Or use the UITraitCollection method performAsCurrent 直接解析动态颜色,然后将解析颜色的代码放在闭包中。上面的简短回答显示了两种方式。

您也可以将您的代码移动到其中一种方法中。在这种情况下,我想你可以把它放在 layoutSubviews() 中。如果这样做,它会在 light/dark 样式更改时自动调用,因此您无需执行任何其他操作。

参考

WWDC 2019, Implementing Dark Mode in iOS

从 19:00 开始,我谈到了如何解析动态颜色,在 23:30 我展示了如何将 CALayer 的边框颜色设置为动态颜色的示例,就像你在做的那样。

您可以在您的视图上添加不可见的子视图,它会跟踪 traitCollectionDidChange 事件。

示例:

import UIKit

extension UIButton {
    func secondaryStyle() {
        backgroundColor = .clear
        layer.cornerRadius = 8
        layer.borderWidth = 1
        layer.borderColor = UIColor(named: "borderColor")?.cgColor

        moon.updateStyle = { [weak self] in
            self?.secondaryStyle()
        }
    }
}

extension UIView {
    var moon: Moon {
        (viewWithTag(Moon.tag) as? Moon) ?? .make(on: self)
    }

    final class Moon: UIView {
        static var tag: Int = .max - 1

        var updateStyle: (() -> Void)?

        static func make(on view: UIView) -> Moon {
            let moon = Moon()
            moon.tag = Moon.tag
            view.addSubview(moon)
            return moon
        }

        override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
            super.traitCollectionDidChange(previousTraitCollection)
            guard previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle else { return }
            triggerUpdateStyleEvent()
        }

        func triggerUpdateStyleEvent() {
            updateStyle?()
        }
    }
}