13 | 功能组件:如何设置动态字体,提升视力辅助功能?

[toc]

前言

本文来自拉勾网课程整理

如今在App开发当中,支持动态字体已成为标配。 2019Airbnb 统计,有 30%iOS用户没有使用默认的字体大小。这说明什么呢?说明越来越多的用户更喜欢依据自己的习惯来设置字体的大小来符合他们的阅读习惯。

那什么是动态字体(Dynamic Type)呢?动态字体实际上就是允许用户选择屏幕上显示文本内容的大小。它能帮助一些用户把字体变大来提高可读性,也能方便一些用户把字体变小,使得屏幕能显示更多内容。

8639c6e41c15d0fb5e49f7b3426a0143

以上就是动态字体的效果,一般在设置App->辅助功能->显示与字体大小->更大字体里面通过拖动滑动条来改变系统字体的大小。

目前流行的App都已经支持动态字体,假如我们的App不支持,当用户在不同App之间切换的时候就会感觉到很唐突,甚至会因为阅读体验的问题而直接删除。

支持动态字体

那么怎样才能让 iOS App 支持动态字体呢?我们需要为显示文本的组件,例如UILabel,UITextViewUIButton指定能自动调整大小的字体。比如下面是为UILabel增加动态字体支持的代码。

label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true

首先,我们使用了UIFont.UITextStyle.body来创建一个UIFont的实例并赋值给 Labelfont属性。 然后把该 Label的adjustsFontForContentSizeCategory设置为true来让它响应用户的动态字体设置。这个属性默认值就为true,假如我们不想让文本自动支持动态字体,可以把它设为false

目前,iOS系统为我们提供了Large Title,Title 1Body11种字体风格,你可以在苹果官方的 《Human Interface Guidelines 》文档里查看它们的具体规范。如下图所示,我从中截取当用户选择默认大小的情况下各种字体风格所对应的字体粗细和大小等信息。其中Large Title的字号是 34ptTitle128pt,它们的字体粗细都是“Regular”

f7b3eb92e4be2026c98c690742e8f85d

人机界面指南

为第三方字体库加入动态字体支持

绝大多数情况下,我们应该使用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方法,并把返回值赋给Labelfont。这样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的实例。例如,为了大号的字体display1display2不会无限放大,我们在生成它们的时候把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属性即可,例如UILabelfont属性以及UIButtontitleLabel

测试动态字体

当我们的 App支持了动态字体以后,在开发过程中需要及时测试,否则可能会不小心引入UIBug。幸运的是 Xcode为我们带来一个名叫Accessibility Inspector的工具来简化动态字体的测试流程。

怎么使用它呢?请看下面的动图:

[图片上传失败...(image-366f26-1621819735135)]

它使用方法很简单,我们可以在Accessibility Inspector工具里选择运行Moments AppSimulator,然后点击 Settings 按钮,接着拖动滑动条来改变Font size 的大小,以此来测试App对动态字体的响应情况。

总结

a99064a7168e84b07f45a9100ccb889f

最后,在加入了动态字体支持后,建议你需要注意以下几点。

  • 要经常使用Accessibility Inspector工具来测试带文本内容的UI,保证所有文本都能正常显示。
  • 不要硬编码文本组件所在容器的高度和宽度,容器的高度和宽度应该随着文本的大小而伸缩,否则当用户选择大字体的时候,可能导致部分文本被遮挡。
  • 除了特殊情况,不要硬编码UILable组件文本显示的行数,否则可能导致文本显示不全。
  • 并不是所有文本都需要支持动态字体,例如 Tabbar 上的标题就需要指定静态的字体大小。

源码地址:

自定义字体集合的文件地址:https://github.com/lagoueduCol/iOS-linyongjian/blob/main/Frameworks/DesignKit/src/Font/UIFontExtensions.swift

你可能感兴趣的:(13 | 功能组件:如何设置动态字体,提升视力辅助功能?)