大多数浏览器和
Developer App 均支持流媒体播放。
-
动态字体体验入门
借助“动态字体”,用户可以选择自己喜欢的文本大小来应用到整个系统和各款 App 之中。为了帮助你轻松开始提供“动态字体”支持,我们将介绍以下基础知识:该功能的运作方式、如何查找 App 在文本缩放方面的问题,以及如何使用 SwiftUI 和 UIKIt 采取实用的步骤来打造出色的“动态字体”体验。我们还将介绍如何充分运用 Large Content Viewer,让人人都能轻松使用导航控件。
章节
- 0:00 - Introduction
- 3:11 - Scaled text
- 6:00 - Dynamic layouts
- 8:56 - Images and symbols
- 11:58 - Large content viewer
资源
-
下载
大家好 我叫 Gaeth 是辅助功能团队的工程师 在 Apple 我们关注 各种辅助功能需求 由于大部分内容都需要文本来传达 因此我很高兴与大家谈谈 视觉辅助功能的一个重要方面: 用户如何阅读和浏览 App 中的文本 以及动态字体如何 帮助打造人人可享的出色阅览体验 动态字体可让用户选择 文本在整个系统 和各款 App 中显示的大小 许多人在使用 App 时 都会自定这一设置 因此我们需要为这项功能提供支持 以便用户充分利用 App 提供的所有功能 如果你从来没有考虑过 你的 App 如何适应大号字体 本讲座将非常适合你!
创建动态 UI 还能 助你构建理想的界面 来适配各种屏幕尺寸、 显示方向和平台 这一点非常重要 因为不同的用户 喜欢或需要的字号各不相同 动态字体可以提高 所有字号的可读性 用户可以根据自己的需要更改字号 如果你的 App 还没有 使用过这些大字号 请前往“辅助功能”设置试一试! 为了自定字号大小 用户可导航至“辅助功能”设置 依次选择“显示与文字大小”> “更大字体” 默认情况下 有 7 种可用的 字号可供选择 如果启用了“更大的辅助功能字体” 就会多出 5 种可用的字号 还可以在“控制中心”添加 “文字大小”控件 以便快速更改首选的字号
文本默认显示为大字号
字号发生变化时 就会立即生效 在本例中 在“设置”App 中 更改为大字号后 每个文本视图的大小就会自动增大 包括标题、正文 和每个单元格中的标签 这个表格视图中的布局也会 增大每个子视图的大小 从而适应内容变大时的尺寸 在本例中 选择了辅助功能字体后 内容增大到超出显示屏的范围 用户只需滚动一下 就能阅读所有文本 在本视频中 我将介绍一些技术 帮助你确保 App 能够利用动态字体缩放内容 并进行相应的调整 从而为用户提供最佳体验 首先 我将探讨如何使用动态字体 然后 我将讨论如何 针对更大字号调整布局流畅性 我将探索有关 内联图像和符号的选项 最后 我还将介绍如何通过 Large Content Viewer 来调整那些可能无法 与其他内容一起缩放的控件
首先 让我们来探讨如何缩放文本 说到在 App 中使用动态字体 出色的用户体验 始于内置文本样式的灵活运用 App 不提供固定的字体 而是从系统提供的 文本样式中选择一种 例如 正文样式可为多行文本创造 舒适的阅读体验 标题样式有助于区分 标题和周围的内容 使用这些样式 App 文本可自动调整为 用户在设备上选择的不同字号 同时保留内容的视觉层次结构
要在 SwiftUI 中使用内置文本样式 你可以使用 font 修饰符 例如 通过传递 title 参数 来选择标题样式 要在 UIKit 中使用文本样式 请将 UILabel 的 adjustsFontForContentSizeCategory 属性设为 true 这样一来 系统字号发生变化时 标签就会自动更新字体 然后 使用 preferredFont 为 textStyle 设置 所需的字体样式 考虑将标签上的行数设置为零 以便文本能够占用所需的行数 避免截断 检查你的 App 中提供的 动态字体使用体验时 需要查找几类问题 例如 如果没有足够的行数 来显示文本 大文本就可能出现截断 文本也可能出现剪切 因为文本容器的框架是固定的 幸运的是 这些问题 一开始就很容易发现 查找问题时 最好用的一款工具 就是 Xcode 预览 如果使用的是 SwiftUI 在 Xcode 中 需要导航到“Preview”画布 点按“Variants”按钮 然后选择 “Dynamic Type Variants”
Xcode 将为可用字号的 每个变体生成预览 因此你可以快速找到 特定视图的问题 你也可以点按 Xcode 画布中的 设置按钮 选择特定的字号 另一种测试字号的方法 是使用 Xcode 调试器 你可以点按设置图标 来覆盖动态字体和 其他辅助功能设置 你还可以对 App 进行审核 查找辅助功能问题 审核会检查 App 的视图层次结构 并查找各种辅助功能问题 如剪切文本、标签缺失 或对比度过低
使用系统字体对应的文本样式 是开始使用 动态字体的好方法 为你的 App 完善 大号文本使用体验时 你还可以考虑调整内容的布局 打造最佳体验 例如 在“通讯录”App 中 创建新的名片海报时 海报选项会以水平堆栈显示 当字号增大时 布局 会动态切换为垂直堆栈 以便每个单元格占据整个显示宽度
在这种情况下 你可能希望 针对大号文本改变布局 但保留默认或小号文本的布局
请看这个 App 四个人形符号以水平堆栈对齐显示 每个人形符号下方都有一个标签 如站立人形和推轮椅人形 虽然使用默认的大文本字号 阅读这些标签没有问题 但在使用更大的辅助功能字体时 效果就不太理想了 每个人形符号的宽度 无法为任一标签提供足够的空间 让我们从单个单元格开始 FigureCell 是一个 VStack 包含图像和标题 为了在 SwiftUI 中实现动态布局 我将使用 dynamicTypeSize 环境键路径 然后 我将定义一个 AnyLayout 类型属性 名为 dynamicLayout 如果用户选择辅助功能字体 这个属性将解析为 HStackLayout 如果用户选择任何其他字体 这个属性将解析为 VStackLayout 然后 我会将 body 中的布局 从 VStack 更新为 dynamicLayout 现在 我只需为单元格 使用垂直布局即可! 在包含视图中 我将按照 相同的步骤 在使用辅助功能字体时 将主内容视图切换为 VStackLayout 在其他情况下则切换为 HStackLayout
好极了 现在 当字号变为辅助功能字体时 布局就会动态更改 为文字提供更多宽度 使文字阅读起来更轻松 同时避免截断
要使用 UIKit 做到这一点 可以考虑使用 UIStackView 堆栈视图提供了垂直或水平安排 子视图布局 所需的所有逻辑 只需更新 axis 属性即可
要确定 axis 请使用 isAccessibilityCategory 属性 这个属性位于视图控制器 traitCollection 的 首选内容大小类别中
要在 App 运行时 响应字号变化 请订阅 UI 内容大小类别 didChangeNotification 并将堆栈视图更新为最佳 axis 使用大字号时 除布局外 与文本内容一起出现的图像和符号 可能也需要进行特定调整 处理大文本、图像和图标时 你需要调整图标随大字号放大的程度 同时确保图标不会占用过多文本空间 在这两者之间取得最佳平衡 例如 我们来看看 iPhone 上的“设置”App 表格视图中的每个项目都包含 一个文本标签和一个装饰图像 字号变大时 即使文本标签变大了 装饰图像的大小也不会变大
这是因为 App 应该优先放大重要内容 而不是装饰视图 如果决定不放大图像 请确保 文本在未放大的图像下换行显示 以充分利用可用空间 在极少数情况下 或许可以在显示最大字号时 删除纯装饰性视图 不过 你应确保删除视图不会导致 功能和重要内容丢失 让我们来探讨一下如何在 SwiftUI 和 UIKit 中实现类似功能 在 SwiftUI 中 在图标下很容易 令文本换行显示 将视图包含在列表中 就能令文本在图标下换行显示 无需任何额外操作! 在列表视图之外 你还可以直接 在文本中插入图像 在 UIKit 中 要实现这种行为 可以使用 NSAttributedString 创建一个属性字符串 将图像作为 NSTextAttachment 并将它附加到属性字符串中
在某些情况下 你可能 希望图像也能调整大小 尤其是当图像中的文本或关键图标 与其余内容相关时
如果图像是 SF Symbol SF Symbol 会自动调整大小! 不过 如果你的素材中有 图像或 PDF 你可以使用 ScaledMetric API 让图像根据所选字号 调整大小 只需添加 ScaledMetric 并指定 图像的宽度或高度 当字号发生变化时 宽度值或高度值 将在运行时自动增减
太棒了!当我增大字号时 图像和文本会 自动缩放!
要在 UIKit 中实现这种行为 可使用 UIImage SymbolConfiguration 例如 可以使用带 textStyle 的 SymbolConfiguration 创建具有特定文本样式的配置 然后使用这个符号配置 创建 UIImage 最后 让我们来了解一下 Large Content Viewer Large Content Viewer 允许你 探索那些可能无法 随着字号而增大的控件 让我向你展示一下具体的运作方式! iPhone 上的“时钟”App 在显示屏 底部有四个标签页 轻点并按住这些标签页时 Large Content Viewer 就会出现在中央 并显示你正在查看的标签页的 大尺寸标签和图标
你可以滑动到其他标签页 然后抬起手指 导航到这个标签页
在这种情况下 标签栏所占空间 不到屏幕高度的 10% 如果在启用大文本时 增加标签栏的高度 它几乎会占据四分之一的屏幕 标签栏会一直显示 因此必须确保 App 的内容仍然是主要焦点 如果你使用的是 系统提供的默认控制栏 则无需任何操作! 系统已提供这些支持 但是 如果你选择 实现自定标签栏或视图 最好在必要时采用 Large Content Viewer
例如 SwiftUI 中的自定标签栏 可能是这样构建的 在这段代码中 HStack 包含 一个按钮 用于显示每个标签页 绑定允许标签栏跟踪所选项目
要添加对 Large Content Viewer 的支持 请使用 accessibilityShowsLargeContentViewer 修饰符 并提供一个 应在查看器中显示的标签 如按钮的名称和符号
要在 UIKit 中支持 Large Content Viewer 请让视图遵循 UILargeContentViewerItem 协议 并实现所需的属性 以提供标题、图像 以及指示何时进行显示的属性 然后 创建 UILargeContentViewerInteraction 的实例 并将它添加到视图中 如果你的控件自带手势识别器 则需要执行额外的操作 让 Large Content Viewer 首先处理手势
请通过 Large Content Viewer 交互部分的 gestureRecognizerForExclusionRelationship 来设置 与其他手势识别器的 识别或失败关系
现在你已经知道如何 开始使用动态字体 请使用大号文本测试一下你的 App! 探索在哪些方面可进行优化 可以使用系统定义的文本样式 还可调整布局以便优先考虑 文本的可读性 请记住 用户喜欢或需要的字号 各不相同 让 App 动态响应各种需求 可以帮助你的 App 为每一位用户提供出色的阅览体验! 要进一步优化你的 App 可将辅助功能审核融合到 UI 测试中 以便在 App 的每次迭代中 及时发现关于动态字体的问题 你还可以查看视频 “了解 SwiftUI 中的辅助功能” 进一步了解 SwiftUI 的辅助技术 感谢观看!
-
-
3:53 - Built-in text styles with SwiftUI
// Use built-in text styles with SwiftUI import SwiftUI struct ContentView: View { var body: some View { Text("Hello, World!") .font(.title) } }
-
4:06 - Built-in text styles in UIKit
// Built-in text styles in UIKit import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let label = UILabel(frame: .zero) setupConstraints() label.text = "Hello, World!" label.adjustsFontForContentSizeCategory = true label.font = .preferredFont(forTextStyle: .title1) label.numberOfLines = 0 self.view.addSubview(label) } }
-
7:20 - Dynamic layout in SwiftUI
// Dynamic layout in SwiftUI import SwiftUI struct FigureCell: View { @Environment(\.dynamicTypeSize) private var dynamicTypeSize: DynamicTypeSize var dynamicLayout: AnyLayout { dynamicTypeSize.isAccessibilitySize ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout()) } let systemImageName: String let imageTitle: String var body: some View { dynamicLayout { FigureImage(systemImageName: systemImageName) FigureTitle(imageTitle: imageTitle) } } }
-
7:52 - Dynamic layout in SwiftUI
// Dynamic layout in SwiftUI import SwiftUI struct FigureContentView: View { @Environment(\.dynamicTypeSize) private var dynamicTypeSize: DynamicTypeSize var dynamicLayout: AnyLayout { dynamicTypeSize.isAccessibilitySize ? AnyLayout(VStackLayout(alignment: .leading)) : AnyLayout(HStackLayout(alignment: .top)) } var body: some View { dynamicLayout { FigureCell(systemImageName: "figure.stand", imageTitle: "Standing Figure") FigureCell(systemImageName: "figure.wave", imageTitle: "Waving Figure") FigureCell(systemImageName: "figure.walk", imageTitle: "Walking Figure") FigureCell(systemImageName: "figure.roll", imageTitle: "Rolling Figure") } } }
-
8:20 - Dynamic layout in UIKit
// Dynamic layout in UIKit import UIKit class ViewController: UIViewController { private var mainStackView: UIStackView = UIStackView() required init?(coder: NSCoder) { super.init(coder: coder) NotificationCenter.default.addObserver(self, selector: #selector(textSizeDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) } override func viewDidLoad() { super.viewDidLoad() setupStackView() } @objc private func textSizeDidChange(_ notification: Notification?) { let isAccessibilityCategory = self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory mainStackView.axis = isAccessibilityCategory ? .vertical : .horizontal setupConstraints() } }
-
10:12 - Scale inline images with SwiftUI
// Inline images in SwiftUI import SwiftUI struct ContentView: View { var body: some View { List { FigureListCell(figureName: "Standing Figure", systemImage: "figure.stand") FigureListCell(figureName: "Rolling Figure", systemImage: "figure.roll") FigureListCell(figureName: "Waving Figure", systemImage: "figure.wave") FigureListCell(figureName: "Walking Figure", systemImage: "figure.walk") } } }
-
10:30 - Scale inline images with UIKit
// Inline images in UIKit func attributedStringWithImage(systemImageName: String, imageTitle: String) -> NSAttributedString { let attachment = NSTextAttachment() attachment.image = UIImage(systemName: systemImageName) let attachmentAttributedString = NSMutableAttributedString(attachment: attachment) attachmentAttributedString.append(NSAttributedString(string: imageTitle)) return attachmentAttributedString }
-
11:05 - Scale images in SwiftUI
// Scaling images in SwiftUI import SwiftUI struct ContentView: View { @ScaledMetric var imageWidth = 125.0 var body: some View { VStack { Image("Spatula") .resizable() .aspectRatio(contentMode: .fit) .frame(width: imageWidth) Text("Grill Party!") .frame(alignment: .center) } } }
-
11:38 - Scale symbols with UIKit
// Symbol configuration in UIKit import UIKit func imageWithBodyConfiguration(systemImageName: String) -> UIImage? { let imageConfiguration = UIImage.SymbolConfiguration(textStyle: .body) let configuredImage = UIImage(systemName: systemImageName, withConfiguration: imageConfiguration) return configuredImage }
-
13:15 - Add large content viewer support with SwiftUI
// Large content viewer support in SwiftUI import SwiftUI struct FigureBar: View { @Binding var selectedFigure: Figure var body: some View { HStack { ForEach(Figure.allCases) { figure in FigureButton(figure: figure, isSelected: selectedFigure == figure) .onTapGesture { selectedFigure = figure } .accessibilityShowsLargeContentViewer { Label(figure.imageTitle, systemImage: figure.systemImage) } } } } }
-
13:45 - Add large content viewer support with UIKit
// Large content viewer support in UIKit import UIKit class FigureCell: UIStackView { var systemImageName: String! var imageTitle: String! var imageLabel: UILabel! var titleImageView: UIImageView! required init(coder: NSCoder) { super.init(coder: coder) setupFigureCell() } init(systemImageName: String, imageTitle: String) { super.init(frame: .zero) self.systemImageName = systemImageName self.imageTitle = imageTitle setupFigureCell() self.addInteraction(UILargeContentViewerInteraction()) self.showsLargeContentViewer = true self.largeContentImage = UIImage(systemName: systemImageName) self.scalesLargeContentImage = true self.largeContentTitle = imageTitle } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。