BonMot (发音为 Bon Mo,法语意为好词) 是一个 Swift 富文本字符串库。它抽象了 iOS、macOS、tvOS 和 watchOS 字体排印工具的复杂性,让您可以专注于美化文本。
要运行示例项目,请运行 pod try BonMot
,或者克隆存储库,打开 BonMot.xcodeproj
,然后运行 Example-iOS 目标。
BonMot 已经被 sherlocked!(指苹果公司推出的类似功能)。如果您的目标是 iOS 15 或更高版本,您可能需要查看 AttributedString。如果您是 BonMot 的现有用户,并且使用的是 Xcode 13,您可能需要在您的项目中添加以下 typealias
以避免与 Foundation.StringStyle
冲突
typealias StringStyle = BonMot.StringStyle
在任何您想使用 BonMot 的 Swift 文件中,只需 import BonMot
。
使用 StringStyle
指定富文本字符串的样式。然后,在 String
上使用 styled(with:)
方法来获取您的富文本字符串
let quote = """
I used to love correcting people’s grammar until \
I realized what I loved more was having friends.
-Mara Wilson
"""
let style = StringStyle(
.font(UIFont(name: "AmericanTypewriter", size: 17)!),
.lineHeightMultiple(1.8)
)
let attributedString = quote.styled(with: style)
// You can also get the style’s attributes dictionary
// if you’re using an API that requires it.
let attributes = style.attributes
以下是您在使用 BonMot 构建富文本字符串时最常交互的类型。
StringStyle
: 可用于设置字符串样式的属性集合。这些包括字体和颜色等基本属性,以及段落控制和 OpenType 功能等更高级的设置。要了解 BonMot 支持的完整功能集,请查看此结构体的接口。StringStyle.Part
: 一个枚举,可用于简洁地构造 StringStyle
。您通常会与这些交互,而不是逐个属性地构造 StringStyle
。Composable
: 一个协议,定义了任何知道如何将自身附加到富文本字符串的类型。BonMot 提供了函数,例如此示例中的函数,用于将多个 Composable
值连接在一起。NamedStyles
: 使用它在全局命名空间中注册自定义的可重用样式。Special
: 一个实用程序,用于在您的字符串中包含特殊、不明确和非打印字符,而不会使您的代码难以阅读。样式可以相互继承,这使您可以创建多个共享通用属性的样式
let baseStyle = StringStyle(
.lineHeightMultiple(1.2),
.font(UIFont.systemFont(ofSize: 17))
)
let redStyle = baseStyle.byAdding(.color(.red))
let blueStyle = baseStyle.byAdding(.color(.blue))
let redBirdString = "bird".styled(with: redStyle)
let blueBirdString = "bird".styled(with: blueStyle)
您是否尝试仅设置字符串的一部分样式,甚至是一个本地化的字符串,它根据应用程序的语言环境而有所不同?没问题!BonMot 可以将自定义 XML 标签和简单的 HTML 转换为富文本字符串
// This would typically be a localized string
let string = "one fish, two fish, <red>red fish</red>,<BON:noBreakSpace/><blue>blue fish</blue>"
let redStyle = StringStyle(.color(.red))
let blueStyle = StringStyle(.color(.blue))
let fishStyle = StringStyle(
.font(UIFont.systemFont(ofSize: 17)),
.lineHeightMultiple(1.8),
.color(.darkGray),
.xmlRules([
.style("red", redStyle),
.style("blue", blueStyle),
])
)
let attributedString = string.styled(with: fishStyle)
这将产生
请注意使用
<BON:noBreakSpace/>
来指定字符串中的特殊字符。这是一种将特殊字符添加到本地化字符串的好方法,因为本地化人员可能不知道要查找特殊字符,并且其中许多字符在普通的文本编辑器中查看时是不可见或不明确的。您可以使用Special
枚举中的任何字符,或使用<BON:unicode value='A1338'/>
或&#a1338;
如果上述方法遇到无效的 XML,则生成的字符串将是整个原始字符串,包括标签。如果您正在解析不受您控制的 XML,例如来自服务器的可变内容,您可能需要使用此替代解析机制,它允许您处理解析时遇到的错误
let rules: [XMLStyleRule] = [
.style("strong", strongStyle),
.style("em", emStyle),
]
let xml = // some XML from a server
do {
let attrString = try NSAttributedString.composed(ofXML: xml, rules: rules)
}
catch {
// Handle errors encountered by Foundation's XMLParser,
// which is used by BonMot to parse XML.
}
BonMot 使用 NSTextAttachment
将图像嵌入到字符串中。您可以使用 BonMot 的 NSAttributedString.composed(of:)
API 将图像和文本链接到同一个字符串中
let someImage = ... // some UIImage or NSImage
let attributedString = NSAttributedString.composed(of: [
someImage.styled(with: .baselineOffset(-4)), // shift vertically if needed
Special.noBreakSpace, // a non-breaking space between image and text
"label with icon", // raw or attributed string
])
请注意使用
Special
类型,它使您可以轻松访问 UI 中常用的 Unicode 字符,例如空格、破折号和非打印字符。
输出
如果需要在图像后换行多行文本,请使用 Tab.headIndent(...)
将整个段落与图像对齐
let attributedString = NSAttributedString.composed(of: [
someImage.styled(with: .baselineOffset(-4.0)), // shift vertically if needed
Tab.headIndent(10), // horizontal space between image and text
"This is some text that goes on and on and spans multiple lines, and it all ends up left-aligned",
])
输出
您可以轻松地使 BonMot 生成的任何富文本字符串响应系统文本大小控制。只需将 .adapt
添加到任何样式声明,并指定您希望样式像 .control
还是像 .body
文本一样缩放。
let style = StringStyle(
.adapt(.control)
// other style parts can go here as needed
)
someLabel.attributedText = "Label".styled(with: style).adapted(to: traitCollection)
如果您希望富文本字符串适应当前的内容大小类别,在 UI 元素上设置时,请使用 .adapted(to: traitCollection)
,如上面的示例所示。
如果在应用程序设置代码中的某个时刻调用 UIApplication.shared.enableAdaptiveContentSizeMonitor()
,BonMot 将在首选内容大小类别更改时更新常见的 UI 元素。您可以通过使其符合 AdaptableTextContainer
协议,将您的自定义控件选择加入自动更新。
如果您想要对自适应过程进行更多的手动控制,并且目标是 iOS 10+,请跳过启用自适应内容大小监视器,并在 traitCollectionDidChange(_:)
中调用 .adapted(to: traitCollection)
。iOS 10 在 UITraitCollection
上引入了 preferredContentSizeCategory
属性。
.control
和 .body
行为都以相同的方式缩放,除了在启用“更大动态类型”辅助功能设置时,.body
会无限增长。这是 系统动态类型样式的默认行为图
您可以注册全局命名样式,并通过 IBInspectable
在 Storyboard 和 XIB 中使用它们
let style = StringStyle(
.font(UIFont(name: "Avenir-Roman", size: 24)!),
.color(.red),
.underline(.styleSingle, .red)
)
NamedStyles.shared.registerStyle(forName: "MyHeadline", style: style)
然后,您可以在 Interface Builder 的 Attributes Inspector 中,在常见的 UIKit 控件(例如按钮和标签)上使用 MyHeadline
如果这些相同的命名样式在 解析的 XML 中用作标签名称,也会被拾取。
使用 bonMotDebugString
和 bonMotDebugAttributedString
打印出任何富文本字符串的版本,并将所有特殊字符和图像附件扩展为人类可读的 XML
NSAttributedString.composed(of: [
image,
Special.noBreakSpace,
"Monday",
Special.enDash,
"Friday"
]).bonMotDebugString
// Result:
// <BON:image size='36x36'/><BON:noBreakSpace/>Monday<BON:enDash/>Friday
您可以使用 XML 规则将生成的字符串(图像除外)重新解析回富文本字符串。您还可以保存 bonMotDebugString
的输出,并使用它来验证单元测试中的富文本字符串。
UIKit 允许您按顶部、底部或基线对齐标签。BonMot 包含 TextAlignmentConstraint
,这是一个布局约束子类,允许您按大写字母高度和 x 高度对齐标签。对于某些字体,这对于表达设计师的意图至关重要
TextAlignmentConstraint
适用于任何公开 font
属性的视图。它使用键值观察来监视 font
属性的更改,并相应地调整其内部测量值。这非常适合与动态类型一起使用:如果用户更改应用程序的字体大小,TextAlignmentConstraint
将更新。您还可以使用它将标签与普通视图对齐,如上例中的红色虚线视图所示。
警告: TextAlignmentConstraint
保留对其 firstItem
和 secondItem
属性的强引用。确保受此约束约束的视图也未对所述约束保留强引用,因为它会导致保留循环。
您可以以编程方式或在 Interface Builder 中使用 TextAlignmentConstraint
。在代码中,像这样使用它
TextAlignmentConstraint.with(
item: someLabel,
attribute: .capHeight,
relatedBy: .equal,
toItem: someOtherLabel,
attribute: .capHeight).isActive = true
在 Interface Builder 中,首先使用 top
约束将两个视图约束在一起。选择约束,然后在 Identity Inspector 中,将类更改为 TextAlignmentConstraint
接下来,切换到 Attributes Inspector。TextAlignmentConstraint
通过 IBInspectables 公开了两个文本字段。键入您要对齐的属性。如果输入无效值,您将收到运行时错误。
布局不会在 Interface Builder 中更改(约束子类不支持 IBDesignable),但它会在您运行代码时起作用。
注意: 并非所有配置都支持某些可能的对齐值。查看 Issue #37 以获取更新。
BonMot 是用 Swift 编写的,但如果您必须在 Objective-C 代码库中使用它,则有一些选择
UILabel *label = [[UILabel alloc] init];
label.bonMotStyleName = @"MyHeadline";
BonMot 4 是一个重大更新,但有一些常见模式可以用来简化过渡。请注意,这主要适用于使用 BonMot 3 的 Swift 项目。BonMot 4+ 对 Objective-C 的支持有限,因此如果您需要维护 Objective-C 兼容性,请在尝试升级之前查看该部分。
BonMot 4 引入了 StringStyle
结构体,它封装了样式信息。当您将 StringStyle
应用于纯 String
时,结果是 NSAttributedString
。这与 BonMot 3 不同,其中 BONChain
或 BONText
同时包含样式和字符串信息。内容与样式的解耦遵循 HTML/CSS 的脚步,并使其更容易测试和推理每个组件,使其与另一个组件分离。
支持内联样式所需的更改非常小。由于 4.0 中进行的一些重命名,它不会是一个完全机械的过程,但它应该相当简单明了
let chain = BONChain()
.color(myColor)
.font(myFont)
.figureSpacing(.Tabular)
.alignment(.Center)
.string(text)
label.attributedText = chain.attributedString
label.attributedText = text.styled(
with:
.color(myColor),
.font(myFont),
.numberSpacing(.monospaced), // renamed in 4.0
.alignment(.center)
)
在 BonMot 3 中,您可能存储了 BONChain
以供以后使用。您可以使用 BonMot 4 的 StringStyle
完成同样的事情,但有一个主要区别:虽然 BONChain
可以包含一个字符串,但 StringStyle
从不这样做。它应用于字符串,生成 NSAttributedString
enum Constants {
static let myChain = BONChain()
.color(myColor)
.font(myFont)
.tagStyles([
"bold": myBoldChain,
])
}
// and then, later:
let attrString = myChain.string("some string").attributedString
enum Constants {
static let myStyle = StringStyle(
.color(myColor),
.font(myFont),
.xmlRules([
.style("bold", myBoldStyle),
]))
}
// and then, later:
let attrString = "some string".styled(with: Constants.myStyle)
BonMot 可通过 Swift Package Manager 获得。要通过 Xcode 安装它,请转到 File -> Swift Packages -> Add Package Dependency...
并粘贴存储库 URL
https://github.com/Rightpoint/BonMot.git
BonMot 可通过 CocoaPods 获得。要安装它,只需将以下行添加到您的 Podfile
pod 'BonMot'
BonMot 也与 Carthage 兼容。要安装它,只需将以下行添加到您的 Cartfile
github "Rightpoint/BonMot"
欢迎提出问题和 pull 请求!请确保在提交之前安装了最新的 SwiftLint,并且构建时没有生成样式警告。
贡献者应遵守贡献者公约行为准则。
Zev Eisenberg: @ZevEisenberg
Logo设计者:Jon Lopkin: @jonlopkin
BonMot 使用 MIT 许可证。 详情请参阅 LICENSE 文件。