NSTextAttachment 图片不是动态的(light/dark 模式)
NSTextAttachment images are not dynamic (light/dark mode)
我有一个 light/dark 模式的动态图像。如果我将此图像放在 UIImageView 中,动态会起作用:当用户从浅色模式切换到深色模式并返回时,图像会更改显示的自身版本。但是,如果我将与 NSTextAttachment 相同的图像放在 NSAttributedString 中,并在标签中显示该字符串,则动态不起作用:当用户从亮模式切换到暗模式时,图像不会改变。
要查看实际问题,请将此代码粘贴到您的 viewDidLoad
:
let size = CGSize(width: 20, height: 20)
let renderer = UIGraphicsImageRenderer(size: size)
let image1 = renderer.image {
UIColor.red.setFill()
[=11=].fill(.init(origin: .zero, size: size))
}
let image2 = renderer.image {
UIColor.green.setFill()
[=11=].fill(.init(origin: .zero, size: size))
}
let asset = UIImageAsset()
asset.register(image1, with: .init(userInterfaceStyle: .light))
asset.register(image2, with: .init(userInterfaceStyle: .dark))
let iv = UIImageView(image: image1)
iv.frame.origin = .init(x: 100, y: 100)
self.view.addSubview(iv)
let text = NSMutableAttributedString(string: "Howdy ", attributes: [
.foregroundColor: UIColor(dynamicProvider: { traits in
switch traits.userInterfaceStyle {
case .light: return .red
case .dark: return .green
default: return .red
}
})
])
let attachment = NSTextAttachment(image: image1)
let attachmentCharacter = NSAttributedString(attachment: attachment)
text.append(attachmentCharacter)
let label = UILabel()
label.attributedText = text
label.sizeToFit()
label.frame.origin = .init(x: 100, y: 150)
self.view.addSubview(label)
我特意使文本字体颜色动态化,以便您可以看到通常颜色动态 在属性字符串中起作用。但不在属性字符串的文本附件中!
所以:这真的是 iOS 的行为方式,还是我在配置文本附件的方式上犯了一些错误?如果这就是 iOS 的行为方式,您是如何解决这个问题的?
[请注意,我不能使用 iOS 15 附件视图提供程序,因为我必须与 iOS 13 和 14 兼容。所以也许这可以解决问题,但解决方案是不对我开放。]
不幸的是,我认为这是正常行为,但我认为它是 Apple 遗忘的功能 non-dev。
我目前得到的唯一方法是监听 func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
以检测模式变化。
然后,您可以重建 NSAttributedString
,或者枚举它并在需要时更新它。
使用枚举,即仅更新需要的内容,而不是重新生成整个 NSAttributedString
:
在您最初创建的附件中:
let attachment = NSTextAttachment(image: asset.image(with: traitCollection))
let attachmentCharacter = NSAttributedString(attachment: attachment)
旁注:
我使用 asset.image(with: traitCollection)
而不是 image1
,否则当从暗模式开始时,您的图像将改为亮模式。所以这应该设置正确的图像。
然后,我会更新它:
func switchAttachment(for attr: NSAttributedString?) -> NSAttributedString? {
guard let attr = attr else { return nil }
let mutable = NSMutableAttributedString(attributedString: attr)
mutable.enumerateAttribute(.attachment, in: NSRange(location: 0, length: mutable.length), options: []) { attachment, range, stop in
guard let attachment = attachment as? NSTextAttachment else { return }
guard let asset = attachment.image?.imageAsset else { return }
attachment.image = asset.image(with: .current)
mutable.replaceCharacters(in: range, with: NSAttributedString(attachment: attachment))
}
return mutable
}
更新时间:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
label.attributedText = switchAttachment(for: label.attributedText)
}
仅作记录,这是我对 Larme 的建议的重写。我将其作为 UILabel 的扩展:
extension UILabel {
func updateAttachments() {
guard let attributedString = attributedText else { return }
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
attributedString.enumerateAttribute(.attachment, in: .init(location: 0, length: attributedString.string.utf16.count), options: []) { value, range, stop in
guard let attachment = value as? NSTextAttachment else { return }
guard let image = attachment.image else { return }
guard let asset = image.imageAsset else { return }
attachment.image = asset.image(with: .current)
mutableAttributedString.setAttributes([.attachment: attachment], range: range)
}
attributedText = mutableAttributedString
}
}
现在,假设我有对麻烦的 UILabel 的引用:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
guard let previousTraitCollection = previousTraitCollection else { return }
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
label.updateAttachments()
}
}
将覆盖注入所有 UILabel 非常好,这样它们就可以自我更新,但不幸的是,扩展不能那样工作,而且我无法将我应用程序中的所有 UILabel 转换为 UILabel 子类.
我有一个 light/dark 模式的动态图像。如果我将此图像放在 UIImageView 中,动态会起作用:当用户从浅色模式切换到深色模式并返回时,图像会更改显示的自身版本。但是,如果我将与 NSTextAttachment 相同的图像放在 NSAttributedString 中,并在标签中显示该字符串,则动态不起作用:当用户从亮模式切换到暗模式时,图像不会改变。
要查看实际问题,请将此代码粘贴到您的 viewDidLoad
:
let size = CGSize(width: 20, height: 20)
let renderer = UIGraphicsImageRenderer(size: size)
let image1 = renderer.image {
UIColor.red.setFill()
[=11=].fill(.init(origin: .zero, size: size))
}
let image2 = renderer.image {
UIColor.green.setFill()
[=11=].fill(.init(origin: .zero, size: size))
}
let asset = UIImageAsset()
asset.register(image1, with: .init(userInterfaceStyle: .light))
asset.register(image2, with: .init(userInterfaceStyle: .dark))
let iv = UIImageView(image: image1)
iv.frame.origin = .init(x: 100, y: 100)
self.view.addSubview(iv)
let text = NSMutableAttributedString(string: "Howdy ", attributes: [
.foregroundColor: UIColor(dynamicProvider: { traits in
switch traits.userInterfaceStyle {
case .light: return .red
case .dark: return .green
default: return .red
}
})
])
let attachment = NSTextAttachment(image: image1)
let attachmentCharacter = NSAttributedString(attachment: attachment)
text.append(attachmentCharacter)
let label = UILabel()
label.attributedText = text
label.sizeToFit()
label.frame.origin = .init(x: 100, y: 150)
self.view.addSubview(label)
我特意使文本字体颜色动态化,以便您可以看到通常颜色动态 在属性字符串中起作用。但不在属性字符串的文本附件中!
所以:这真的是 iOS 的行为方式,还是我在配置文本附件的方式上犯了一些错误?如果这就是 iOS 的行为方式,您是如何解决这个问题的?
[请注意,我不能使用 iOS 15 附件视图提供程序,因为我必须与 iOS 13 和 14 兼容。所以也许这可以解决问题,但解决方案是不对我开放。]
不幸的是,我认为这是正常行为,但我认为它是 Apple 遗忘的功能 non-dev。
我目前得到的唯一方法是监听 func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
以检测模式变化。
然后,您可以重建 NSAttributedString
,或者枚举它并在需要时更新它。
使用枚举,即仅更新需要的内容,而不是重新生成整个 NSAttributedString
:
在您最初创建的附件中:
let attachment = NSTextAttachment(image: asset.image(with: traitCollection))
let attachmentCharacter = NSAttributedString(attachment: attachment)
旁注:
我使用 asset.image(with: traitCollection)
而不是 image1
,否则当从暗模式开始时,您的图像将改为亮模式。所以这应该设置正确的图像。
然后,我会更新它:
func switchAttachment(for attr: NSAttributedString?) -> NSAttributedString? {
guard let attr = attr else { return nil }
let mutable = NSMutableAttributedString(attributedString: attr)
mutable.enumerateAttribute(.attachment, in: NSRange(location: 0, length: mutable.length), options: []) { attachment, range, stop in
guard let attachment = attachment as? NSTextAttachment else { return }
guard let asset = attachment.image?.imageAsset else { return }
attachment.image = asset.image(with: .current)
mutable.replaceCharacters(in: range, with: NSAttributedString(attachment: attachment))
}
return mutable
}
更新时间:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
label.attributedText = switchAttachment(for: label.attributedText)
}
仅作记录,这是我对 Larme 的建议的重写。我将其作为 UILabel 的扩展:
extension UILabel {
func updateAttachments() {
guard let attributedString = attributedText else { return }
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
attributedString.enumerateAttribute(.attachment, in: .init(location: 0, length: attributedString.string.utf16.count), options: []) { value, range, stop in
guard let attachment = value as? NSTextAttachment else { return }
guard let image = attachment.image else { return }
guard let asset = image.imageAsset else { return }
attachment.image = asset.image(with: .current)
mutableAttributedString.setAttributes([.attachment: attachment], range: range)
}
attributedText = mutableAttributedString
}
}
现在,假设我有对麻烦的 UILabel 的引用:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
guard let previousTraitCollection = previousTraitCollection else { return }
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
label.updateAttachments()
}
}
将覆盖注入所有 UILabel 非常好,这样它们就可以自我更新,但不幸的是,扩展不能那样工作,而且我无法将我应用程序中的所有 UILabel 转换为 UILabel 子类.