Font Descriptor:字体描述符的关键概念与功能

作者: IT Sectr 发布日期: 2026-07-23 阅读时间: 9 分钟

Font Descriptor — 是 iOS 中的 UIFontDescriptor 和 macOS 中的 NSFontDescriptor 对象,它封装了字体的所有元数据:字族、字型、大小、字间距、行间距和排版属性。开发人员无需直接通过 UIFont(name:size:) 创建字体,而是通过描述符描述所需特征,系统从可用字体中选出最佳匹配。根据 Apple Developer Documentation,UIFontDescriptor 支持超过 20 种字体属性,并用于所有 iOS 排版框架中 — 从 UIKit 到 Core Text 再到 SwiftUI。

要点

  • Font Descriptor — 包含字族、字型、大小和排版属性元数据的字体描述对象
  • 允许从属性集合创建 UIFont,无需指定具体字体名称
  • 支持 修改 现有字体:更改大小、字型、字距而无需创建新对象
  • 用于 UIKit、Core Text 和 SwiftUI 中的字体搜索和配置
  • 提供 字体匹配 — 自动选择最接近的可用字型

什么是 Font Descriptor 及为什么需要它

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 个。

Font Descriptor 的主要属性和特性

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 中的所有排版场景。

属性类型描述
UIFontDescriptorFamilyString字体家族 (SF Pro, Helvetica Neue)
UIFontDescriptorNameString字体全名 (HelveticaNeue-Bold)
UIFontDescriptorSizeNSNumber字体大小(以磅为单位)
UIFontDescriptorMatrixNSValue仿射变换矩阵
UIFontDescriptorTraits[String: Any]字型的符号特征
UIFontDescriptorTextStyleStringDynamic Type 样式 (headline, body, caption)

在 Swift 中通过 Font Descriptor 创建 UIFont

在 iOS 中使用 Font Descriptor 的主要方式 — 创建描述符然后生成 UIFont。当您需要获取具有特定特征的字体但不知道确切名称时,这是首选方法。该过程包括两个步骤:通过初始化器创建 UIFontDescriptor 并在描述符上调用 font(withSize:) 方法。

swift
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 自动根据用户设置调整大小:

swift
// 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 增加字母间距。这提供了更易读的代码,无需加载单独的字体。

swift
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 版本之间变化的变体列表。

swift
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 和 CTFontDescriptor 的集成

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 结合使用可提供对应用程序排版的完全控制。

常见问题

Font Descriptor 与 UIFont 有何不同?

UIFont — 是可直接使用的字体,用于渲染文本。Font Descriptor — 是字体的描述,是可以创建 UIFont 的属性集合。一个描述符可以生成多个不同大小的 UIFont。

Font Descriptor 可以用于自定义字体吗?

可以。如果字体在系统中注册(通过 Info.plist 或 Core Graphics),其描述符可以按 家族名称创建,与系统字体完全相同。匹配适用于所有已注册的字体。

如何从描述符获取字体名称?

使用 descriptor.object(forKey: .visibleName) 获取显示名称,或 descriptor.postscriptName 获取 PostScript 名称。PostScript 名称在通过 UIFont(name:size:) 创建字体时使用。

Font Descriptor 中的 cascade list 是什么?

Cascade list — 是嵌套描述符的数组,用作后备字体。如果主字体不包含所需字符,系统会依次检查 cascade list 中的字体。用于支持 emoji 和 CJK 字符。

Font Descriptor 在 SwiftUI 中有效吗?

是的。SwiftUI 通过 Font(descriptor:size:) 支持 Font Descriptor。SwiftUI 包装器接受 UIFontDescriptor 并创建一个 SwiftUI 字体,保留描述符的所有属性 — 字距、连字和字型。

总结

  • Font Descriptor — 具有家族、字型、大小和排版特性属性的字体描述对象
  • 允许从属性集合创建 UIFont,带有自动字体匹配
  • 通过 addingAttributes、withSize、withSymbolicTraits 支持不可变的 修改
  • matchingFontDescriptors 返回所有可用字型用于字体搜索和选择
  • 通过 UIFontDescriptor 和 CTFontDescriptor 之间的 toll-free bridging 与 Core Text 集成
  • 通过首选文本样式和缩放支持 Dynamic Type
  • 推荐用于支持所有 iOS 版本的自适应排版,无需检查字体可用性

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读