仅更改 AttributedString 的字符串(例如 UIButton 配置 attributeTitle)

mat*_*att 2 ios swift attributedstring

从 iOS 15 开始,Swift 提供了 AttributedString 结构,它包含字符串的文本及其样式属性。问题:给定一个现有的 AttributedString,并假设(为了简单起见)属性由单个样式运行组成,如何仅更改AttributedString 的字符串部分?

\n
\n

这是一个典型的用例 \xe2\x80\x94 UIButton。假设我有一个基于配置的按钮,带有属性标题:

\n
let button = UIButton(configuration: .plain())\nlet font = UIFont(name: "Georgia", size: 16)\nbutton.configuration?.attributedTitle = AttributedString(\n    "Hello", attributes: AttributeContainer.font(font!)\n)\n
Run Code Online (Sandbox Code Playgroud)\n

如果我稍后将配置的标题设置为不同的标题,则属性信息将丢失。例如:

\n
button.configuration?.title = "Goodbye"\n// Button title is no longer in Georgia font!\n
Run Code Online (Sandbox Code Playgroud)\n

显然,我在这里想做的是替换属性字符串标题的文本而不干扰其属性。但 Swift 的 AttributedString 似乎没有提供一种方法来做到这一点。

\n

因此,正如我一开始就问的:正确的方法是什么?

\n

mat*_*att 6

更改 AttributedString 的文本非常棘手。您必须将属性字符串的字符视图 \xe2\x80\x94 的内容替换为其characters属性。更困难的是,您不能简单地通过分配另一个字符串来做到这一点!

\n

以我们的按钮为例,这不会编译:

\n
button.configuration?.attributedTitle?.characters = "Goodbye" // error\n
Run Code Online (Sandbox Code Playgroud)\n

从简单的字符串中导出字符视图也是不够的。这也不能编译:

\n
button.configuration?.attributedTitle?.characters = "Goodbye".characters // error\n
Run Code Online (Sandbox Code Playgroud)\n

这是因为简单字符串的单独字符视图不再存在;您仍在尝试将字符串分配给字符视图,我们已经知道您不能这样做。

\n

相反,您可以直接从字符串创建 AttributedString.CharacterView 并将分配到目标属性字符串的characters属性中。Swift 推断类型在这里有很大帮助:

\n
button.configuration?.attributedTitle?.characters = .init("Goodbye")\n
Run Code Online (Sandbox Code Playgroud)\n

这会替换按钮的标题,而不会影响按钮标题的样式属性。

\n

有关按钮的更多信息

\n

替换按钮的标题是一件非常有用的事情,我在 UIButton 上做了一个小实用程序扩展,涵盖了所有情况 \xe2\x80\x94 一个不基于配置的按钮,一个基于配置的按钮基于配置但没有属性标题,以及一个基于配置并具有属性标题的按钮:

\n
extension UIButton {\n    func replaceTitle(_ newTitle: String) {\n        guard configuration != nil else {\n            setTitle(newTitle, for: .normal)\n            return\n        }\n        guard configuration?.attributedTitle != nil else {\n            configuration?.title = newTitle\n            return\n        }\n        configuration?.attributedTitle?.characters = .init(newTitle)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

更多关于按钮的信息

\n

人们可能(并且已经)问,在基于配置的按钮的情况下,为什么要使用attributedTitle?为什么不在按钮配置中设置字体buttonConfig.titleTextAttributesTransformer

\n

答案是,这不适用于需要动态响应用户更改其文本大小首选项的现实生活字体。要明白我的意思,请尝试这个例子:

\n
let button = UIButton(configuration: .plain())\nlet font = UIFontMetrics(forTextStyle: .subheadline)\n    .scaledFont(for: UIFont(name: "Georgia", size: 16)!)\nbutton.configuration?.title = "Hello"\nbutton.configuration?.titleTextAttributesTransformer = .init { container in\n    container.merging(AttributeContainer.font(font))\n}\n
Run Code Online (Sandbox Code Playgroud)\n

您将看到,尽管按钮标题最初以正确的字体显示,并且尽管该字体在设置按钮的配置标题后仍然存在,但字体大小的动态性已经丢失。对于动态大小的字体,您必须将字体设置为属性标题的一部分。

\n

事实上,这个用例最终激发了我最初的问题,并因此产生了这个答案。

\n