[toc]
前言
本文来自拉勾网课程整理
如今在App
开发当中,支持动态字体已成为标配。 2019
年 Airbnb
统计,有 30%
的iOS
用户没有使用默认的字体大小。这说明什么呢?说明越来越多的用户更喜欢依据自己的习惯来设置字体的大小来符合他们的阅读习惯。
那什么是动态字体(Dynamic Type
)呢?动态字体实际上就是允许用户选择屏幕上显示文本内容的大小。它能帮助一些用户把字体变大来提高可读性,也能方便一些用户把字体变小,使得屏幕能显示更多内容。
以上就是动态字体的效果,一般在设置App->辅助功能->显示与字体大小->更大字体
里面通过拖动滑动条来改变系统字体的大小。
目前流行的App
都已经支持动态字体,假如我们的App
不支持,当用户在不同App
之间切换的时候就会感觉到很唐突,甚至会因为阅读体验的问题而直接删除。
支持动态字体
那么怎样才能让 iOS App
支持动态字体呢?我们需要为显示文本的组件,例如UILabel,UITextView
和UIButton
指定能自动调整大小的字体。比如下面是为UILabel
增加动态字体支持的代码。
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
首先,我们使用了UIFont.UITextStyle
的.body
来创建一个UIFont
的实例并赋值给 Label
的font
属性。 然后把该 Label
的adjustsFontForContentSizeCategory
设置为true
来让它响应用户的动态字体设置。这个属性默认值就为true
,假如我们不想让文本自动支持动态字体,可以把它设为false
。
目前,iOS
系统为我们提供了Large Title,Title 1
和Body
等11
种字体风格,你可以在苹果官方的 《Human Interface Guidelines 》
文档里查看它们的具体规范。如下图所示,我从中截取当用户选择默认大小的情况下各种字体风格所对应的字体粗细和大小等信息。其中Large Title
的字号是 34pt
,Title1
是 28pt
,它们的字体粗细都是“Regular”
。
人机界面指南
为第三方字体库加入动态字体支持
绝大多数情况下,我们应该使用iOS
系统提供的内置字体库。但也有一些例外,例如使用自定义字体库来强调自身品牌,或者使用搞怪字体为游戏提供沉浸式体验。这个时候怎么办呢?我们可以使用第三方字体库,同时为它配置动态字体的支持。代码示例如下:
guard let customFont = UIFont(name: "CustomFont", size: UIFont.labelFontSize) else {
fatalError("Failed to load the "CustomFont" font. Make sure the font file is included in the project and the font name is spelled correctly."
)
}
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
-
第一步
是通过传递字体库的名字,来加载并初始化类型为UIFont
的对象customFont
。 -
第二步
是传入字体风格.headline
,来初始化一个UIFontMetrics
的对象。 -
第三步
是把customFont
传入scaledFont(for font: UIFont) -> UIFont
方法,并把返回值赋给Label
的font
。这样label
就能即使用第三方的字体库又能支持动态字体。
Moments App 的字体定义
和大部分的App
一样,我们没有在Moments App
里使用第三方字体库。而是根据 07
章的设计规范,在 DesignKit
组件里面实现了自定义的字体集合,具体代码如下:
public extension UIFont {
static let designKit = DesignKitTypography()
struct DesignKitTypography {
public var display1: UIFont {
scaled(baseFont: .systemFont(ofSize: 42, weight: .semibold), forTextStyle: .largeTitle, maximumFactor: 1.5)
}
public var display2: UIFont {
scaled(baseFont: .systemFont(ofSize: 36, weight: .semibold), forTextStyle: .largeTitle, maximumFactor: 1.5)
}
public var title1: UIFont {
scaled(baseFont: .systemFont(ofSize: 24, weight: .semibold), forTextStyle: .title1)
}
public var title2: UIFont {
scaled(baseFont: .systemFont(ofSize: 20, weight: .semibold), forTextStyle: .title2)
}
public var title3: UIFont {
scaled(baseFont: .systemFont(ofSize: 18, weight: .semibold), forTextStyle: .title3)
}
public var title4: UIFont {
scaled(baseFont: .systemFont(ofSize: 14, weight: .regular), forTextStyle: .headline)
}
public var title5: UIFont {
scaled(baseFont: .systemFont(ofSize: 12, weight: .regular), forTextStyle: .subheadline)
}
public var bodyBold: UIFont {
scaled(baseFont: .systemFont(ofSize: 16, weight: .semibold), forTextStyle: .body)
}
public var body: UIFont {
scaled(baseFont: .systemFont(ofSize: 16, weight: .light), forTextStyle: .body)
}
public var captionBold: UIFont {
scaled(baseFont: .systemFont(ofSize: 14, weight: .semibold), forTextStyle: .caption1)
}
public var caption: UIFont {
scaled(baseFont: .systemFont(ofSize: 14, weight: .light), forTextStyle: .caption1)
}
public var small: UIFont {
scaled(baseFont: .systemFont(ofSize: 12, weight: .light), forTextStyle: .footnote)
}
}
}
private extension UIFont.DesignKitTypography {
func scaled(baseFont: UIFont, forTextStyle textStyle: UIFont.TextStyle = .body, maximumFactor: CGFloat? = nil) -> UIFont {
let fontMetrics = UIFontMetrics(forTextStyle: textStyle)
if let maximumFactor = maximumFactor {
let maximumPointSize = baseFont.pointSize * maximumFactor
return fontMetrics.scaledFont(for: baseFont, maximumPointSize: maximumPointSize)
}
return fontMetrics.scaledFont(for: baseFont)
}
}
我们为UIFont
定义了一个类型扩展(Extension
)。为了调用的时候具有命名空间,我们在这个扩展里面定义了一个名叫DesignKitTypography
的内嵌结构体(Nested struct
),然后定义了一个静态属性来引用该结构体。
根据之前设计规范里面的字体定义,我们在DesignKitTypography
结构体里面分别定义了display1、display2、title1
等一系列的字体属性。比如display1
用于页面唯一的大标题,title1
用于第一级段落标题,body
用于正文等等,它们都调用了同一个私有方法scaled(baseFont: UIFont, forTextStyle textStyle: UIFont.TextStyle = .body, maximumFactor: CGFloat? = nil)
来生成一个支持动态字体的UIFont
。这里的scaled
方法是怎样实现的呢?
根据之前设计规范里面的字体定义,我们在DesignKitTypography
结构体里面分别定义了display1、display2、title1
等一系列的字体属性。比如display1
用于页面唯一的大标题,title1
用于第一级段落标题,body
用于正文等等,它们都调用了同一个私有方法scaled(baseFont: UIFont, forTextStyle textStyle: UIFont.TextStyle = .body, maximumFactor: CGFloat? = nil)
来生成一个支持动态字体的UIFont
。这里的scaled
方法是怎样实现的呢?
首先,该方法通过传递进来的textStyle
参数初始化一个UIFontMetrics
对象。这样能保证我们自定义的字体会以 iOS
自带的TextStyle
作为基准来进行缩放,然后判断maximumFactor
是否为空。
如果不为空就计算出maximumPointSize
并调用scaledFont(for font: UIFont, maximumPointSize: CGFloat)
方法来返回一个UIFont
的实例。例如,为了大号的字体display1
和display2
不会无限放大,我们在生成它们的时候把maximumFactor
设置为1.5
。如果maximumFactor
为空,我们就调用scaledFont(for font: UIFont)
方法并直接返回UIFont
的实例。
有了DesignKitTypography
结构体的定义,以后需要增加新的字体类型也非常简单,只需要定义新字体的名字、字体粗细和大小就可以了。例如在这里我新增caption2
的代码,它也使用了系统自带的字体库,并把字体大小设为10pt
,字体粗细设为细体,同时使用了.caption2
作为基准字体风格。 代码示例如下:
public var caption2: UIFont {
scaled(baseFont: .systemFont(ofSize: 10, weight: .light), forTextStyle: .caption2)
}
完成了这些字体集合的定义以后,我们可以在代码中很方便地使用它们。代码如下:
label1.font = UIFont.designKit.title1
button.titleLabel?.font = UIFont.designKit.bodyBold
我们可以通过UIFont.designKit
取出支持动态字体的UIFont
类型并赋值给对应的font
属性即可,例如UILabel
的font
属性以及UIButton
的titleLabel
。
测试动态字体
当我们的 App
支持了动态字体以后,在开发过程中需要及时测试,否则可能会不小心引入UI
的 Bug
。幸运的是 Xcode
为我们带来一个名叫Accessibility Inspector
的工具来简化动态字体的测试流程。
怎么使用它呢?请看下面的动图:
[图片上传失败...(image-366f26-1621819735135)]
它使用方法很简单,我们可以在Accessibility Inspector
工具里选择运行Moments App
的 Simulator
,然后点击 Settings
按钮,接着拖动滑动条来改变Font size
的大小,以此来测试App
对动态字体的响应情况。
总结
最后,在加入了动态字体支持后,建议你需要注意以下几点。
- 要经常使用
Accessibility Inspector
工具来测试带文本内容的UI
,保证所有文本都能正常显示。 - 不要硬编码文本组件所在容器的高度和宽度,容器的高度和宽度应该随着文本的大小而伸缩,否则当用户选择大字体的时候,可能导致部分文本被遮挡。
- 除了特殊情况,不要硬编码
UILable
组件文本显示的行数,否则可能导致文本显示不全。 - 并不是所有文本都需要支持动态字体,例如
Tabbar
上的标题就需要指定静态的字体大小。
源码地址:
自定义字体集合的文件地址:https://github.com/lagoueduCol/iOS-linyongjian/blob/main/Frameworks/DesignKit/src/Font/UIFontExtensions.swift