BonMot Logo

Swift 5.0 CircleCI Version License Platform Carthage compatible Swift Package Manager compatible codecov

BonMot (发音为 Bon Mo,法语意为好词) 是一个 Swift 富文本字符串库。它抽象了 iOS、macOS、tvOS 和 watchOS 字体排印工具的复杂性,让您可以专注于美化文本。

要运行示例项目,请运行 pod try BonMot,或者克隆存储库,打开 BonMot.xcodeproj,然后运行 Example-iOS 目标。

AttributedString

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 构建富文本字符串时最常交互的类型。

样式继承

样式可以相互继承,这使您可以创建多个共享通用属性的样式

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'/>&#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 会无限增长。这是 系统动态类型样式的默认行为图

Graph of iOS Dynamic Type scaling behavior, showing that most text tops out at the XXL size, but Body, Large Title, and Title 1 text keeps growing all the way up through AccessibilityXXXL

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 的 Attributes Inspector 中,在常见的 UIKit 控件(例如按钮和标签)上使用 MyHeadline

Editing the Bon Mot Style Name attribute in the Attributes Inspector of Interface Builder, and setting the value to MyHeadline

如果这些相同的命名样式在 解析的 XML 中用作标签名称,也会被拾取。

调试和测试助手

使用 bonMotDebugStringbonMotDebugAttributedString 打印出任何富文本字符串的版本,并将所有特殊字符和图像附件扩展为人类可读的 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 高度对齐标签。对于某些字体,这对于表达设计师的意图至关重要

Illustration of different methods of aligning text vertically

TextAlignmentConstraint 适用于任何公开 font 属性的视图。它使用键值观察来监视 font 属性的更改,并相应地调整其内部测量值。这非常适合与动态类型一起使用:如果用户更改应用程序的字体大小,TextAlignmentConstraint 将更新。您还可以使用它将标签与普通视图对齐,如上例中的红色虚线视图所示。

警告: TextAlignmentConstraint 保留对其 firstItemsecondItem 属性的强引用。确保受此约束约束的视图也未对所述约束保留强引用,因为它会导致保留循环。

您可以以编程方式或在 Interface Builder 中使用 TextAlignmentConstraint。在代码中,像这样使用它

TextAlignmentConstraint.with(
    item: someLabel,
    attribute: .capHeight,
    relatedBy: .equal,
    toItem: someOtherLabel,
    attribute: .capHeight).isActive = true

在 Interface Builder 中,首先使用 top 约束将两个视图约束在一起。选择约束,然后在 Identity Inspector 中,将类更改为 TextAlignmentConstraint

setting the class in the Identity Inspector

接下来,切换到 Attributes Inspector。TextAlignmentConstraint 通过 IBInspectables 公开了两个文本字段。键入您要对齐的属性。如果输入无效值,您将收到运行时错误。

setting the alignment attributes in the Attributes Inspector

布局不会在 Interface Builder 中更改(约束子类不支持 IBDesignable),但它会在您运行代码时起作用。

注意: 并非所有配置都支持某些可能的对齐值。查看 Issue #37 以获取更新。

Objective-C 兼容性

BonMot 是用 Swift 编写的,但如果您必须在 Objective-C 代码库中使用它,则有一些选择

UILabel *label = [[UILabel alloc] init];
label.bonMotStyleName = @"MyHeadline";

BonMot 3 → 4+ 迁移指南

BonMot 4 是一个重大更新,但有一些常见模式可以用来简化过渡。请注意,这主要适用于使用 BonMot 3 的 Swift 项目。BonMot 4+ 对 Objective-C 的支持有限,因此如果您需要维护 Objective-C 兼容性,请在尝试升级之前查看该部分。

将样式与内容分离

BonMot 4 引入了 StringStyle 结构体,它封装了样式信息。当您将 StringStyle 应用于纯 String 时,结果是 NSAttributedString。这与 BonMot 3 不同,其中 BONChainBONText 同时包含样式和字符串信息。内容与样式的解耦遵循 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 Package Manager

BonMot 可通过 Swift Package Manager 获得。要通过 Xcode 安装它,请转到 File -> Swift Packages -> Add Package Dependency... 并粘贴存储库 URL

https://github.com/Rightpoint/BonMot.git

CocoaPods

BonMot 可通过 CocoaPods 获得。要安装它,只需将以下行添加到您的 Podfile

pod 'BonMot'

Carthage

BonMot 也与 Carthage 兼容。要安装它,只需将以下行添加到您的 Cartfile

github "Rightpoint/BonMot"

贡献

欢迎提出问题和 pull 请求!请确保在提交之前安装了最新的 SwiftLint,并且构建时没有生成样式警告。

贡献者应遵守贡献者公约行为准则

作者

Zev Eisenberg: @ZevEisenberg

Logo设计者:Jon Lopkin: @jonlopkin

许可协议

BonMot 使用 MIT 许可证。 详情请参阅 LICENSE 文件。