BonMot(发音为 Bon Mo,法语中意为“好词”)是一个 Swift 格式化字符串库。它抽象了 iOS、macOS、tvOS 和 watchOS 字体工具的复杂性,让您可以专注于让您的文本变得美丽。
要运行示例项目,请运行 pod try BonMot
,或克隆仓库,打开 BonMot.xcodeproj
,并运行 Example-iOS 目标。
AttributedString
BonMot 已经被 sherlocked!如果您正在针对 iOS 15 或更高版本,您可能要查看 AttributedString。如果您是使用 Xcode 13 的 BonMot 现有用户,您可能想在项目中添加以下 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)
使用XML样式字符串的一部分
尝试仅为字符串的一部分应用样式,或许是一个根据应用区域设置而不同的本地化字符串?没问题!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'/>
或
带有错误处理的XML解析
如果上述方法遇到无效的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
会无界增长。以下是系统动态类型样式的默认行为的图表。
Storyboard和XIB集成
您可以注册全局命名样式,并通过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的属性检查器中将MyHeadline
用于常见的UIKit控件,如按钮和标签。
如果这些命名样式作为解析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
属性保持强引用。请确保受该约束约束的视图不会也保持对该约束的强引用,因为这将引起保留周期。
您可以按程序方式或使用界面构建器使用TextAlignmentConstraint
。在代码中,可以这样使用它
TextAlignmentConstraint.with(
item: someLabel,
attribute: .capHeight,
relatedBy: .equal,
toItem: someOtherLabel,
attribute: .capHeight).isActive = true
在界面构建器中,首先用top
约束约束两个视图。选择约束,然后在身份检查器中,将类更改为TextAlignmentConstraint
然后切换到属性检查器。TextAlignmentConstraint
通过IBInspectables公开了两个文本字段。键入您想要对齐的属性。如果您输入了无效值,您将得不到运行时错误。
在界面构建器(IBDesignable不支持约束子类)中,布局不会改变,但运行代码时它会起作用。
注意:某些可能的对齐值在所有配置中可能不受支持。有关更新,请参阅问题#37。
Objective-C 兼容性
BonMot 使用 Swift 语言编写,但如果必须在 Objective-C 代码库中使其起作用,您有几个选择。
- 对于旧Objective-C代码库,考虑使用在 Objective-C 中编写的最后一个主要版本 BonMot 3.2。确保参考该版本标签自带的 ReadMe,因为语法与 BonMot 4.0 及以后的版本不同。
- 如果在使用 Objective-C 和 Swift 的项目中混合使用,可以在 界面构建器部分创建命名样式,然后在 Objective-C 中访问这些样式。
UILabel *label = [[UILabel alloc] init];
label.bonMotStyleName = @"MyHeadline";
- 使用与 界面构建器部分中相同的方式,检查常见 UIKit 元素的 inspectable 属性。
BonMot 3 → 4+ 迁移指南
BonMot 4 是一个重大更新,但有一些常见的模式可以帮助您轻松过渡。注意,这主要适用于之前使用 BonMot 3 的 Swift 项目。由于 BonMot 4+ 对 Objective-C 的支持有限,所以在您需要保持 Objective-C 兼容性之前,请先检查该部分内容。
从内容中分离样式
BonMot 4 引入了 StringStyle
结构体,它可以封装样式信息。当将 StringStyle
应用于纯 String
时,结果是 NSAttributedString
。这与 BonMot 3 不同,在 BonMot 3 中,BONChain
或 BONText
包含了样式和字符串信息。内容与样式的解耦沿袭了 HTML/CSS 的步骤,这使得从其他组件单独测试和推理各个组件变得更加容易。
内联样式
支持内联样式的修改非常小。由于4.0版本中的某些重命名,它不会是一个完全的机械过程,但它应该相当简单。
BonMot 3
let chain = BONChain()
.color(myColor)
.font(myFont)
.figureSpacing(.Tabular)
.alignment(.Center)
.string(text)
label.attributedText = chain.attributedString
BonMot 4
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
。
BonMot 3
enum Constants {
static let myChain = BONChain()
.color(myColor)
.font(myFont)
.tagStyles([
"bold": myBoldChain,
])
}
// and then, later:
let attrString = myChain.string("some string").attributedString
BonMot 4
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)
安装
Swift 包管理器
BonMot 通过 Swift 包管理器 提供。要在 Xcode 中安装,请转到 文件 -> Swift 包 -> 添加依赖项...
并粘贴仓库 URL
https://github.com/Rightpoint/BonMot.git
CocoaPods
BonMot 通过 CocoaPods 提供。要安装它,只需将以下行添加到您的 Podfile 中
pod 'BonMot'
Carthage
BonMot 也与 Carthage 兼容。要安装它,只需将以下行添加到您的 Cartfile 即可:
github "Rightpoint/BonMot"
贡献
欢迎提出问题和拉取请求!请确保在提交前已安装最新版本的 SwiftLint,并在构建时确保没有生成样式警告。
贡献者应遵守 贡献者公约行为准则。
作者
Zev Eisenberg: @ZevEisenberg
Logo 由 Jon Lopkin 设计: @jonlopkin
许可协议
BonMot 在 MIT 许可协议下可用。有关更多信息,请参阅 LICENSE 文件。