Font Descriptor — 是 iOS 中的 UIFontDescriptor 和 macOS 中的 NSFontDescriptor 对象,它封装了字体的所有元数据:字族、字型、大小、字间距、行间距和排版属性。开发人员无需直接通过 UIFont(name:size:) 创建字体,而是通过描述符描述所需特征,系统从可用字体中选出最佳匹配。根据 Apple Developer Documentation,UIFontDescriptor 支持超过 20 种字体属性,并用于所有 iOS 排版框架中 — 从 UIKit 到 Core Text 再到 SwiftUI。
要点
Font Descriptor — 是一个将字体特征描述为键值对集合的对象,而不是具体的字体文件。开发人员不是按名称创建字体,而是指定字族、字型、大小和可选属性,系统选择最佳匹配。这种方法对于自适应排版至关重要:同一描述符可以在不同 iOS 版本上工作,因为这些版本的可用字体集不同。根据 iOS Human Interface Guidelines,通过描述符进行字体匹配是使用动态字体的推荐方式。
如果没有 Font Descriptor,开发人员必须按名称检查每个字体的可用性并手动确定后备方案。描述符自动执行此过程:它包含字型兼容性矩阵 — 常规、粗体、斜体、粗斜体 — 在没有精确匹配时返回最接近的可用字型。系统匹配 在 Core Text 级别工作,并通过统一 的 NSFontDescriptor API 在 iOS 和 macOS 上均可用。
描述符也用于自定义字体 — 通过 Info.plist 加载或手动添加的字体。如果字体在系统中注册,其描述符的创建方式与系统字体完全相同。跨平台说明:描述符的概念在 Android 中也通过 Typeface.Builder 存在,但 iOS 通过 UIFontDescriptor 的实现提供了显著更多的属性 — 超过 20 个,而 Android 只有 5 个。
UIFontDescriptor 包含分组在 fontAttributes 字典(类型为 [UIFontDescriptor.AttributeName: Any])中的属性。关键属性包括字体家族 (family)、名称 (name)、大小 (size)、字型 (traits)、变换矩阵 (matrix)、光学大小 (opticalSize) 和文本样式 (textStyle)。根据 Apple WWDC 2019 Session 227,使用文本样式 — Dynamic Type — 对于可访问性兼容的应用程序是强制性的。
traits 属性 — 是一个包含符号特征的字典:粗细 (UIFontWeightTrait)、斜体 (UIFontSlantTrait)、宽度 (UIFontWidthTrait) 等。这些值由字体匹配器用于选择字型。例如,值为 0.0 的 UIFontWeightTrait — 常规字型,0.3 — 粗体。系统自动将数值映射到可用的字体变体。
其他属性包括用于 OpenType 功能(连字、字距、数字比例)的 featureSettings、用于向用户显示的 visibleName、用于字体仿射变换的 matrix 以及用于后备字体的级联列表 cascadeList。完整的属性列表几乎涵盖了 iOS 和 macOS 中的所有排版场景。
| 属性 | 类型 | 描述 |
|---|---|---|
| UIFontDescriptorFamily | String | 字体家族 (SF Pro, Helvetica Neue) |
| UIFontDescriptorName | String | 字体全名 (HelveticaNeue-Bold) |
| UIFontDescriptorSize | NSNumber | 字体大小(以磅为单位) |
| UIFontDescriptorMatrix | NSValue | 仿射变换矩阵 |
| UIFontDescriptorTraits | [String: Any] | 字型的符号特征 |
| UIFontDescriptorTextStyle | String | Dynamic Type 样式 (headline, body, caption) |
在 iOS 中使用 Font Descriptor 的主要方式 — 创建描述符然后生成 UIFont。当您需要获取具有特定特征的字体但不知道确切名称时,这是首选方法。该过程包括两个步骤:通过初始化器创建 UIFontDescriptor 并在描述符上调用 font(withSize:) 方法。
import UIKit
// 为家族创建字体描述符
let descriptor = UIFontDescriptor.init(
fontAttributes: [
.family: "SF Pro",
.traits: [
.weight: UIFont.Weight.bold
]
]
)
// 从描述符生成 UIFont
let font = descriptor.font(withSize: 17)
这种方法保证系统会为当前设备上的 SF Pro 粗体字型选择最佳匹配。关键优势 — 代码不依赖于 iOS 版本:在 iOS 13 上将使用 SF Pro,在更早版本上 — Helvetica Neue 或默认系统字体。
要创建具有动态大小的字体,请使用带有文本样式和比例的初始化器。Dynamic Type 自动根据用户设置调整大小:
// Dynamic Type 描述符
let bodyDescriptor = UIFontDescriptor
.preferredFontDescriptor(withTextStyle: .body)
// 将字体设为粗体
let boldDescriptor = bodyDescriptor.withSymbolicTraits([.traitBold])
// 生成 UIFont 并保留动态大小
let boldBodyFont = UIFont(
descriptor: boldDescriptor!,
size: bodyDescriptor.pointSize
)
重要:withSymbolicTraits 方法返回可选描述符 — 如果请求的字型不可用,则返回 nil。使用前始终检查可选值。
Font Descriptor 支持不可变模型 — 每个修改方法都返回一个包含已更改属性的新描述符。这符合 值语义,使代码可预测:原始描述符永不变更。主要的修改方法:addingAttributes 用于添加属性,withFace 用于更改字型,withSize 用于更改大小,withMatrix 用于仿射变换。
addingAttributes 方法 — 最灵活。它接受一个属性字典并返回一个新描述符。当键匹配时,新值替换旧值。这允许组合更改:在一次调用中添加权重、更改大小和启用连字。根据 Apple Technical Q&A QA1682,addingAttributes 是通过扩展进行批量修改的推荐方法。
实际应用:在代码编辑器中为等宽文本创建字体。开发人员获取系统等宽描述符,为细线添加降低的权重,并通过 featureSettings 增加字母间距。这提供了更易读的代码,无需加载单独的字体。
let monoDescriptor = UIFontDescriptor.monospacedDigitFontDescriptor()
// 添加自定义属性
let customDescriptor = monoDescriptor.addingAttributes([
.size: 14,
.traits: [
.weight: UIFont.Weight.medium
]
])
let monoFont = UIFont(
descriptor: customDescriptor,
size: 14
)
限制:并非所有属性都可以在创建描述符后修改。例如,字体家族 (family) 在初始化时设置一次。如果需要更改家族,请创建新的描述符。这与 Core Text 的架构有关,其中描述符绑定到特定的字体资源。
字体匹配 — Font Descriptor 的关键功能,它从已安装的字体中自动为请求的属性选择最佳匹配。该过程包括三个阶段:过滤(排除不匹配的字体)、排序(按匹配程度排序)和选择(返回最佳结果)。匹配在 Core Text 级别执行 — 这是所有 iOS 排版基础的框架。
要搜索与描述符匹配的所有字体,请使用 matchingFontDescriptors 方法。它返回按相关性排序的描述符数组。开发人员可以选择精确匹配(第一个元素)或遍历变体以实现自定义逻辑。根据 Apple Font Handling Guide,这是无需按名称枚举即可获取特定家族可用字型列表的唯一方法。
使用场景:在文本设置界面中选择字体时。用户选择一个家族,应用程序通过 matchingFontDescriptors 获取所有可用字型并在选择器中显示。如果没有匹配,您将需要硬编码在不同 iOS 版本之间变化的变体列表。
let searchDescriptor = UIFontDescriptor.init(
fontAttributes: [.family: "SF Mono"]
)
// 获取所有可用字型
let matchingDescriptors = searchDescriptor
.matchingFontDescriptors(withMandatoryKeys: nil)
for descriptor in matchingDescriptors {
print(descriptor.object(forKey: .visibleName) ?? "unnamed")
}
Mandatory keys — 限制搜索的参数。如果指定 [.family],匹配将返回指定家族的所有字体。如果指定 [.family, .traits] — 仅返回在家族和字型上都匹配的字体。使用 mandatory keys 可在搜索字体子集时加快匹配速度。
性能:匹配是一种同步操作,在 iOS 上耗时 0.5 到 5 毫秒。对于单次搜索这可以忽略不计,但在批量遍历描述符时(例如,在包含 50+ 字体的选择器中),建议缓存结果。Apple 建议在加载屏幕时执行一次匹配并保存描述符数组。
Core Text — 是一个用于在 iOS 和 macOS 上进行排版工作的底层 C 框架。它的 UIFontDescriptor 对应物 — CTFontDescriptor,在 Core Foundation 级别提供相同的能力。CTFontDescriptor 在 Core Text API 中用于创建字体、测量文本和渲染复杂的排版。UIFontDescriptor 本质上是围绕 CTFontDescriptor 的 Objective-C 包装器,带有扩展的属性集。
UIFontDescriptor 和 CTFontDescriptor 之间的 Toll-free bridging 允许描述符在 UIKit 和 Core Text 之间无需转换即可传递。这对于使用自定义文本渲染的应用程序至关重要 — 例如基于 TextKit 的文本编辑器或带有语法高亮的代码编辑器。示例:CTFontDescriptorCreateCopyWithAttribute 创建带有添加属性的描述符副本 — 相当于 UIFontDescriptor 的 addingAttributes。
关键属性 kCTFontURLAttribute 在 CTFontDescriptor 中包含字体文件的 URL。这允许确定字体是系统字体还是自定义字体。对于自定义字体,URL 指向字体文件 (.ttf 或 .otf),对于系统字体 — 指向系统字体目录。通过描述符的数据是只读的;通过 URL 修改字体文件不受支持。
对于高级排版 — 自定义连字、上下文替换、替代字形 — 使用带有 kCTFontFeatureSettingsAttribute 键的 CTFontDescriptorCopyAttribute。此属性包含可以通过描述符添加的 OpenType 功能字典数组。根据 Apple Core Text Programming Guide,将 UIFontDescriptor 与 Core Text 结合使用可提供对应用程序排版的完全控制。
常见问题
UIFont — 是可直接使用的字体,用于渲染文本。Font Descriptor — 是字体的描述,是可以创建 UIFont 的属性集合。一个描述符可以生成多个不同大小的 UIFont。
可以。如果字体在系统中注册(通过 Info.plist 或 Core Graphics),其描述符可以按 家族 或 名称创建,与系统字体完全相同。匹配适用于所有已注册的字体。
使用 descriptor.object(forKey: .visibleName) 获取显示名称,或 descriptor.postscriptName 获取 PostScript 名称。PostScript 名称在通过 UIFont(name:size:) 创建字体时使用。
Cascade list — 是嵌套描述符的数组,用作后备字体。如果主字体不包含所需字符,系统会依次检查 cascade list 中的字体。用于支持 emoji 和 CJK 字符。
是的。SwiftUI 通过 Font(descriptor:size:) 支持 Font Descriptor。SwiftUI 包装器接受 UIFontDescriptor 并创建一个 SwiftUI 字体,保留描述符的所有属性 — 字距、连字和字型。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。