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?()
}
}
}
我有一个自定义的 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?()
}
}
}