UIFont和CTFont——是iOS中处理字体的类:UIFont来自UIKit,用于在UILabel和UITextView中进行高级文本配置;CTFont来自Core Text,用于低级渲染,完全控制字形和度量。根据Apple Developer Documentation,UIFont用于90%的标准UI组件,而CTFont应用于自定义文本引擎和编辑器。两个类代表同一个字体,但抽象级别不同。
要点
UIFont——是一个UIKit类,表示用于在UI组件中显示文本的字体:UILabel、UITextView、UITextField。它提供了简单的接口来获取系统和自定义字体,指定大小、粗细和样式。UIFont不允许直接管理字距调整、连字和OpenType功能——这需要使用Core Text。
CTFont——是一个Core Text类型(CTFontRef),提供对字体数据的低级访问。CTFont直接与Core Graphics协作进行渲染,支持OpenType和TrueType的所有功能,包括替代字形、连字和可变字体。CTFont被UIKit内部用于渲染UIFont,但也直接可用于自定义绘制。
UIFont和CTFont支持toll-free bridging——类型之间的零成本转换。在Swift和Objective-C中可以进行UIFont as CTFont或CTFont as UIFont的强制转换。这使得可以在不复制字体数据的情况下,使用高级UIKit API进行配置,使用低级Core Text进行渲染。
两个类都使用UIFontDescriptor(UIKit)/ CTFontDescriptor(Core Text)来描述字体特征:名称、大小、粗细、样式、变换矩阵。描述符是一个可以修改并基于现有字体创建新字体的属性字典。例如,向描述符添加traitItalic以获取同一字体的斜体版本。
UIFont提供了几种工厂方法来创建字体。systemFont(ofSize:)返回系统字体San Francisco(iOS)或Helvetica(旧版本)。boldSystemFont、italicSystemFont——带字样的变体。preferredFont(forTextStyle:)返回给定样式的Dynamic Type字体:.headline、.body、.caption1、.footnote。这些字体在系统文本大小设置更改时自动缩放。
加载自定义字体使用UIFont(name:size:),需要字体的PostScript名称。字体必须预先添加到Info.plist并包含在应用程序的bundle中。根据Apple的建议,建议对所有标准UI组件使用UIFont,仅在需要自定义渲染时才切换到CTFont。
Dynamic Type——在iOS 7中引入的自适应字体系统。用户在设置中选择偏好的文本大小——从超小到超大。UIFont.preferredFont自动返回相应大小的字体。为了正常工作,所有UILabel必须设置adjustsFontForContentSizeCategory = true。Dynamic Type对于辅助功能兼容性是必需的。
UIFontDescriptor允许在不从头创建新实例的情况下修改字体属性。方法addingAttributes(_:)、withSymbolicTraits(_:)、withFamily(_:)返回带有修改参数的新描述符。这对于获取字体变体很方便:向系统字体添加traitBold以获取粗体版本,而无需知道PostScript名称。
CTFont(CTFontRef)——用于低级字体处理的Core Text类型。使用CTFontCreateWithName、CTFontCreateWithFontDescriptor、CTFontCreateCopyWithAttributes函数创建。每个函数返回具有指定接口、大小和可选属性的字体。CTFont在Core Graphics中用于在自定义UIView上通过drawRect渲染文本。
CTFont提供了UIFont中不可用的度量访问:CTFontGetGlyphsForCharacters返回给定字符的字形数组,CTFontGetAdvancesForGlyphs——每个字形的宽度,CTFontGetBoundingBox——字形的边界。这对于构建自定义文本布局是必需的:列、旋转、环绕。
CTFontGetAscent、CTFontGetDescent、CTFontGetLeading返回字体的垂直度量。CTFontGetUnitsPerEm——每个em-square的单位数,用于计算比例。CTFontGetGlyphCount——字体中的字形总数。这些数据对于印刷排版和需要精确定位每个字符的自定义排版至关重要。
CTFont支持OpenType功能:CTFontCopyFeatures返回字体可用功能的字典(连字、替代字符、字距调整),CTFontEnableFeature激活特定选项。可变字体(Variable Fonts)通过CTFontCopyVariation和CTFontCreateCopyWithVariation进行配置,允许在连续范围内更改weight、width和slant。
在Swift中创建UIFont通过工厂方法之一或按名称直接初始化进行。最常见的方法是systemFont(ofSize:weight:)用于系统字体,或preferredFont(forTextStyle:)用于Dynamic Type。对于自定义字体,使用UIFont(name:size:)并指定PostScript名称。
UIFont.systemFont(ofSize:weight:)接受UIFont.Weight——从.ultraLight到.black。此方法返回具有相应粗细的San Francisco系统字体。UIFont.monospacedSystemFont、UIFont.monospacedDigitSystemFont返回用于代码和数字的等宽字体。所有系统字体自动支持Dynamic Type。
let regularFont = UIFont.systemFont(ofSize: 16, weight: .regular)
let boldFont = UIFont.systemFont(ofSize: 16, weight: .bold)
let headlineFont = UIFont.preferredFont(forTextStyle: .headline)
let monoFont = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular)
label.font = headlineFont
每个字体可以直接赋值给UILabel的font属性,或通过NSFontAttributeName在NSAttributedString中使用。系统字体由iOS缓存,因此重复创建系统字体不会产生额外开销。
要使用自定义字体,将TTF或OTF文件添加到项目的bundle中,并在Info.plist(UIAppFonts)中指定它。字体的PostScript名称可以通过UIFont.familyNames和UIFont.fontNames(forFamilyName:)获取。注册后,字体可通过UIFont(name:size:)在应用程序的任何位置使用。
// 获取所有字体家族名称
for family in UIFont.familyNames {
let names = UIFont.fontNames(forFamilyName: family)
print("\(家族): \(名称)")
}
// 加载自定义字体
if let customFont = UIFont(name: "MyCustomFont-Regular", size: 18) {
label.font = customFont
}
如果字体名称不正确,UIFont(name:size:)返回nil。建议在调试时预先通过UIFont.familyNames和UIFont.fontNames(forFamilyName:)打印所有可用名称,以确保字体已正确注册。
加载自定义字体有两种方式:通过Info.plist(UIAppFonts)进行静态注册,和通过CTFontManagerRegisterGraphicsFont进行动态注册。第一种方法更简单——字体在应用程序启动后立即可用。第二种方法用于从网络加载或按需下载的字体。
动态注册对于按需资源很有用:如果应用程序包含20+字体,不必全部包含在初始bundle中,可以在首次使用时下载。CTFontManagerRegisterFontsForURLs从指定的URL注册字体。注册后,字体像往常一样通过UIFont(name:size:)可用。
要通过Core Text进行自定义渲染,使用CTFontCreateWithName并输入字体的PostScript名称。与UIFont的区别在于,CTFont可以使用变换矩阵创建(CTFontCreateWithNameAndOptions)——用于创建变形字体:倾斜、X轴缩放、旋转。这在UIFont中无法实现,除非使用仿射变换。
// 使用倾斜矩阵创建CTFont
var matrix = CGAffineTransform(a: 1, b: 0, c: 0.2, d: 1, tx: 0, ty: 0)
let ctFont = CTFontCreateWithName(
"Helvetica" as CFString,
24, &matrix)
// 使用UIFont的toll-free bridging
let uiFont = ctFont as UIFont
label.font = uiFont
变换矩阵允许创建斜体变体而无需单独的字体文件。参数c(skew)为字符添加倾斜。矩阵还允许水平缩放字体——创建同一字体的压缩或扩展变体。
UIFont和CTFont之间的主要区别是抽象级别。UIFont为常见任务提供简单的API:获取系统字体、更改大小、应用于UILabel。CTFont提供对字体的完全控制:访问字形、度量、OpenType表和可变轴。选择取决于任务。
| 特性 | UIFont | CTFont |
|---|---|---|
| 框架 | UIKit | Core Text |
| 创建 | systemFont / preferredFont / initWithName | CTFontCreateWithName / CTFontCreateWithDescriptor |
| 访问字形 | 否 | CTFontGetGlyphsForCharacters |
| OpenType功能 | 有限(UIFontDescriptor) | 完全访问 |
| 可变字体 | 通过UIFontDescriptor | CTFontCopyVariation / CreateCopyWithVariation |
| 渲染 | 在UI组件中自动 | Core Graphics,自定义 |
对于95%的iOS开发任务,UIFont已经足够。使用CTFont的Core Text在创建文本编辑器、PDF生成器、具有印刷排版的书籍或具有稀有语言自定义显示的应用程序时是合理的。
在iOS中处理字体需要考虑性能。每个独特字体在首次使用时加载到内存并占用资源。San Francisco系统字体预加载且不产生额外开销。自定义字体从文件加载并在首次加载后由系统缓存。
建议将应用程序中的独特字体数量限制在3-5个家族。每个字体占用50 KB到2 MB的内存。使用同一家族的不同粗细(regular、bold、medium)比加载多个不同家族更高效。
系统缓存已创建的UIFont实例,因此重复调用UIFont.systemFont(ofSize: 16, weight: .regular)不会创建新对象。然而,UIFont(name:size:)用于自定义字体不总是被缓存——建议将常用字体存储在静态属性或NSCache缓存中,以避免从文件重复加载。
通过CTFontManagerRegisterGraphicsFont进行动态注册时,如果字体文件从网络下载,过程可以是异步的。在这种情况下,UILabel或UITextView应在加载完成前显示后备系统字体。注册后,字体变为可用,屏幕自动更新文本显示。
常见问题
是的,UIFont和CTFont支持toll-free bridging。转换通过简单的强制转换完成:UIFont as CTFont或CTFont as UIFont。没有额外开销或数据复制——两个类在内存中指向同一个字体对象。
对于所有UI组件,使用UIFont。它针对UIKit进行了优化,支持Dynamic Type,在系统设置更改时正确缩放。CTFont仅在通过Core Graphics进行自定义渲染或需要访问低级字体度量时才是合理的。
使用UIFont.familyNames获取家族列表,使用UIFont.fontNames(forFamilyName:)获取PostScript名称。在调试模式下调用此代码并找到所需字体。PostScript名称通常包含家族名称和字样:“SFProText-Regular”、“HelveticaNeue-Bold”。
是的,通过UIFontDescriptor可以配置可变轴:weight、width、slant。然而,UIFont用于可变字体的API与CTFontCopyVariation相比有限。要完全控制可变轴,请使用Core Text和CTFontCreateCopyWithVariation。
创建一个静态字典或NSCache,键为名称和大小的组合。对于系统字体,iOS会自动缓存实例,但对于通过UIFont(name:size:)创建的自定义字体,建议将对象存储在应用程序缓存中,以避免从TTF文件重复加载。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。