大多数浏览器和
Developer App 均支持流媒体播放。
-
创建 watchOS 的无障碍体验
了解如何在支持较大文本尺寸、旁白和 AssistiveTouch 等功能的同时为 watchOS 打造一流的辅助功能体验。我们将展示专为 watchOS 构建的 SwiftUI app 添加视觉和运动辅助功能支持,包括有关 API 集成、体验等的最佳实践。
资源
相关视频
WWDC21
WWDC20
WWDC19
-
下载
♪ (在watchOS上 创建可访问的体验) 大家好 我是丹尼尔·赛克斯-特纳 我是一名辅助功能工程师 在本视频中 我和同事维拉塔 将介绍 watchOS上的一些 辅助功能 以及开发者如何打造手表应用程序 以便为使用这些功能的人提供支持 在今天的视频中 我将首先为各位介绍 watchOS上的可访问性 然后 我将深入讲解辅助功能 API 并向大家展示如何支持 不同类型的视觉辅助功能 最后 维拉塔将向各位介绍 运动辅助功能在手表上的工作方式 以及您可以做些什么来提供支持 让我们先从watchOS 上的辅助功能说起 辅助功能是指人们 以最适合自己的方式 使用他们的设备 这意味着 想要让您的应用程序 获得最佳用户体验 就必须对辅助功能加以考量 在Apple Watch上 我们有大量的辅助功能 可以让您的应用程序更易于使用 例如 VoiceOver 等辅助技术 可以让视障人士 充分利用他们的 Apple Watch 他们能够通过一系列 手势和点击来导航屏幕 同时程序将以语音方式 向他们播报内容 watchOS今年推出的全新功能 是AssistiveTouch 该功能专为Apple Watch 进行了重新设计 AssistiveTouch 帮助运动障碍患者 在完全不必触摸屏幕的情况下 使用Apple Watch 维拉塔稍后将向各位 展示这是如何工作的 以及您可以做些什么来提供支持 watchOS还提供了 一些显示调解功能 例如Reduce Motion Bold Text 以及今年的新功能 辅助功能大字体 让我们来看看watchOS 上的视觉辅助功能 利用适当的API来 支持辅助功能将确保 VoiceOver能够为 您的用户正确运行 无论您的应用程序是 用WatchKit 还是 SwiftUI编写 本视频将重点介绍针对 SwiftUI的辅助功能 但要知道 您在这里学到的所有原则 也同样适用于WatchKit 在使您的应用程序具有辅助功能时 别忘记表盘小部件和通知 表盘小部件和动态通知 也需要支持我们的辅助技术 因为它们是传递应用程序消息的 另一条途径 现在 学习新知识的最好方法 不就是开发应用程序吗? 幸运的是 我在家种过植物之后 觉得为它们花费精力非常值得 因此决定进一步 开发出一款应用程序来辅助照料它们 现在 程序还没有开发完成 但在主屏幕上 我能看到植物的所有信息 包括接下来的照料任务安排: 五天内浇水 七天内施肥 保持中等的光照量 在这份列表中 还有其他几株植物 对于每株植物 我都有两个按钮 用来记录任务 例如我浇水或施肥的时间 通过点击单元格 我可以调整 浇水或施肥的间隔天数 由于这是一款提供信息的应用程序 每个单元格都包含了大量文本 在这里 我将向大家展示 系统字体为默认大小时 该应用程序的界面 它看起来相当漂亮 但如果我将系统字体调整为特别小 正如左图所示 您将会发现 虽然按钮和任务列表文本 重新调整了字体大小 但植物名称文本的字体大小不变 当我将字体调到特别大时 如右图所示 任务列表字体 变得非常大 以至于被截断了 不再适配显示出屏幕上的所有信息 让我们来看看该应用程序 如何更好地支持 Dynamic Type 如果我检查我的 PlantView代码 会发现我声明了一个包含植物名称 以及其他内容的VStack函数 请注意 标题的字体 使用了固定的字体大小 这确实能够阻止 标题字体大小发生改变 我不应该使用固定的字体大小 而是应该使用系统提供的 11种文本样式之一 左边的文本样式 使用了默认的系统文本大小 当把字体大小调到最大时 就会显示成右边文本字体的大小 通过使用文本样式 系统将根据系统文本大小设置 自动调整字体大小 所以 如果我再次 查看PlantView函数的代码 我可以很快很简单地 将标题字体更改为 字体较小的title3文本样式 接下来 让我们解决 任务列表文本被截断的问题 在PlantTaskLabel 函数代码中 我将HStack函数中所有内容 的lineLimit限制为1 只允许文本单行显示 为了让您的用户界面能够根据需要 灵活地调整行数 请将lineLimit设置为 您需要支持的最大行数 或者删除lineLimit 允许无限行数 现在 我们已经取得了进步 但是 虽然界面字体确实变大了 而且不再被截断 与此同时 界面也变得更加拥挤 屏幕上的信息仍然不易于阅读 有时 对于较大的文本样式 我们只需要以不同的方式构建其布局 所以在构建布局时 我将在sizeCategory中 创建一个 Environment属性包装器 以便发生变化时随时获得更新 接下来 我只需要调整用户界面 使用户界面依赖于 sizeCategory 在这种情况下 任务列表文本 开始以 extraExtrallarge包装 如果sizeCategory 比它更小 我将使用我们之前 看到的PlantView 但如果更大 我将使用这种 垂直布局的植物视图 它将我的标签和按钮都堆叠在一起 使它们有更大空间可以扩展 这样看起来就好多了 随着大字体可访问性文本样式 在手表上的推出 我们期待看到更多人 使用Dynamic Type 您也应该这样做! 现在 人们在设置他们的 Apple Watch时 他们一开始就会看到 自定义文本大小的选项 如果他们不做任何调整 手表将自动选择 与手机上所用字体最相近的大小 概括来讲Dynamic Type 有三个关键要素 可以确保手表应用程序优美地显示 更大的文本 首先 需要确保始终使用文本样式 而不是固定的字体大小 其次 允许文本换行以避免被截断 第三 在必要时 如果内容变得过于拥挤 可切换到垂直堆叠布局 今天关于大文本字体 的讲解只是浅尝辄止 我强烈建议您观看 “用Dynamic Type 打造应用程序”视频 来真正提升您的技巧 想要进一步了解 如何营造出色的视觉体验 请观看 “实现应用程序的 视觉可访问性”视频 好了,应用程序在视觉上表现出色 但如果我患有视觉障碍呢? 让我们打开VoiceOver 感受一下它能带来怎样的听觉体验
WWDaisy 雏菊 图像 水珠 图像 五天 叶子 图像 七天 调高亮度 图像 中等 水滴 填充 按钮 叶子 填充 按钮 好 我们确实 可以做一些改进 首先是减少可进行交互的 元素数量 目前 这个视图相当复杂 每个单元格有四个标签 四张图像和两个按钮 目前 如果想要访问第二株植物 我必须浏览完第一株 植物单元格中的每一项 此外 浇水、施肥和阳光的图像图标 作为文本的单独元素被读出 它们的标签在我们的 语境中甚至没有意义 最后 底部的两个按钮 使用了所用符号提供的 默认标签 因此 我们谈到的第一个问题是 我必须浏览完太多的项 才能访问第二株植物 当我创建我的 NavigationLink时 我将Children的 accessibilityElement分组 指定为.contain 只是为了弄明白每个元素的 可访问性状态 但既然这个看起来不错 我将删除这一行 而NavigationLink 将自动合并 来自children 的 所有可访问性信息 现在 单元格被视为单个元素 文本内容被读取为 “WWDaisy 五天 七天 中等 按钮” 任务列表仍然需要一些改进 所以接下来 我们将为每个任务赋予更好的标签 为接下来的植物照料任务 提供一些背景信息 标签由PlantTask决定 在PlantTaskLabel结构中 我将修改 accessibilityLabel 使得每个植物任务 能够返回不同的字符串 在为我的按钮提供标签时 我也将采用同样的方法 现在 内容被读取为 “WWDaisy 五天内浇水 七天内施肥 保持中等光照 按钮” 浇水和施肥按钮将被读取为 “记录浇水 按钮” 和“记录施肥 按钮” 现在 系统已经为我们 自动完成了许多工作 只需要再加一些修饰符 实际上 有了SwiftUI 您的大部分可访问性都是免费的 您可能只需要编写几行代码 但有些时候 您可能需要创建一个自定义控件 我就创建了一个自定义控件 因而我可以调整 植物浇水与施肥的频率 我们来看一下吧 浇水频率天数 标题 减 按钮 八 加 按钮 加 虽然这在技术上可行 但体验上并不理想 我们的目标是把这三项 变成同一个可访问元素 为此 我首先使用我们的 accessibilityElement 修饰符 这将创建一个新的高级元素 但这次 我将忽略 所有的children 这其实就是 accessibilityElement 的默认行为 所以我可以将参数留空 以获得相同的行为 由于我忽略了children 这将摈弃“加”和“减”按钮标签 以及自动提供的 可访问性行为 区别于上述方法,我将使用 accessibilityAdjustableAction 通过在计算器上向上或向下滑动 来允许用户增加或减少天数 既然我只有一个元素 我将用任务名称 为该元素提供唯一一个标签 最终输出结果为“浇水频率” 或是“施肥频率” 最后 我将赋予它一个值 accessibilityValue 在每次值发生改变时都会被读取 而标签只有调用元素时 才会被读取
浇水频率天数 标题 浇水频率 八天 可调整 九天 很好 这好多了 正如您刚才所见 SwiftUI让 VoiceOver可以轻松地访问 我们的手表应用程序 最棒的是 因为这是SwiftUI 同样的代码也可以在 macOS 和iOS上运行 如欲进一步了解 如何使用SwiftUI 来设计出色的体验 请观看 “SwiftUI的可访问性”视频 如欲进一步了解有关SwiftUI 可访问性的全新工具和API 请观看 “SwiftUI 可访问性: 进阶知识”视频 在我们继续讨论视觉可访问性之前 我想着重介绍另外两项功能: 表盘小部件和通知 表盘小部件是应用程序的高流量窗口 当然 它们也需要以可访问的方式 提供信息 表盘小部件有许多不同的类型 但大多数都包含 三个不同的组件:文本 图像和符号 文本将被VoiceOver 自动选取 但如果您的文本中包含缩写词 请确认使用非缩写版本 添加可访问性标签 这里 我们将缩写的 “Wednesday Mar 9” 改为非缩写的 “Wednesday, March 9th” 基于图像的表盘小部件也很常见 也请为它们提供辅助功能标签 否则将使用图像名称 “月亮”的描述性远远比不上 “月亮的实时视图 第三季度” SF符号等图像 可能拥有默认的辅助功能标签 例如“水滴 填充” 但请确保 符号所带的标签 是对您来说最有意义的标签 “三天内浇水”对我来说更有意义 至于通知 它们是应用程序 向用户提供大量信息的另一个途径 因此有的通知相当简单直观 但动态通知等其他通知 可能拥有复杂的视图 所以需要与应用程序相同的 辅助功能支持 现在我们把镜头交给维拉塔 她将为各位介绍运动辅助功能 维拉塔·尹容妍:好的 谢谢达尼尔 我是维拉塔·尹容妍 我是辅助功能团队的一名工程师 今天 我很高兴能为大家讲解 Apple Watch上的 运动辅助功能支持 但在开始讲解之前 请允许我简单介绍一下我们的新特性 无需触摸屏幕 只需要使用手势 我就能够从表盘待机界面 跳转到控制中心 再到“请勿打扰”按钮 然后打开勿扰模式 就是这么简单 今年 我们非常高兴 将AssistiveTouch 引入Apple Watch AssistiveTouch 让用户无需触摸 即可使用Apple Watch的 全部功能 只需使用戴Apple Watch 的那只手 用户可使用手势或手部动作 来引导光标在屏幕上移动 他们可以打开菜单 来访问基于屏幕内容的 其他功能 对有些人来说 这可能是 他们与Apple Watch 进行交互的唯一方式 对于运动障碍患者 例如截肢 手部或手臂功能缺失 AssistiveTouch 将为他们带来更多选择 让他们能在Apple Watch 上控制和执行操作 现在 让我们举例说明 AssistiveTouch 的使用方式 使用AssistiveTouch 的主要方式 是通过手势 人们能做出不同的手势 例如握拳以进行点击 两次握拳以打开操作菜单 捏一下手指以导航至下一个元素 捏两次则返回至上一个元素 对于无法使用手势的人 他们可以选择手部动作 人们可以通过倾斜手腕 来移动屏幕上的指针 与用户界面元素进行交互 类似于iOS上的 AssistiveTouch 借助Dwell Control 您可以让指针在元素上停留一会儿 以此来执行操作
现在让我们来看看 AssistiveTouch是如何工作的 AssistiveTouch 包含两个主要特性: 光标和操作菜单 打开AssistiveTouch 您将看到一个光标出现在屏幕上 这个光标将聚焦于屏幕上的每个元素 一次一个 按照从左上到右下的顺序 光标会突出显示元素 以进行进一步的交互 要获得关于已聚焦元素的更多操作 您可以打开操作菜单 执行系统或自定义操作 AssistiveTouch 操作菜单拥有 可对设备进行控制的默认系统操作 例如按下数码表冠 滚动导航 手势交互 和其他操作 您也可以向该菜单添加自定义操作 现在 我们知道了 AssistiveTouch是如何工作的 让我们看看如何在应用程序中支持它 我们将讨论以下话题 首先 我们将了解在您的视图中 哪些是可聚焦元素 以及我们如何修改它们 然后 我们将着眼于光标框 以及我们如何修改光标框大小 最后 我们将详细讨论 如何自定义操作菜单 我们先来谈谈可聚焦元素 这张表简要列出了 可被AssistiveTouch 聚焦的元素 只有响应用户交互的交互元素 才是可聚焦的 SwiftUI提供内置的控制元素 来处理用户交互 这些元素是可交互且可聚焦的 如果您的视图中有一个按钮 一个切换键或者一个 NavigationLink 这些元素在默认情况下是可聚焦的 可操作的元素可被 AssistiveTouch聚焦 因为它们有一个动作或者 被定义为可交互 文本元素只要附加一个点击手势动作 就能变得可交互和可聚焦 您也能为一个元素添加 accessibilityAction 如果您将一个元素定义为 具有可操作的特征 比如按钮或者可调整 那么它将被视为交互元素 并且可被 AssistiveTouch聚焦 有些元素是不可聚焦的 不响应用户交互的静态元素 例如标签或文本 不可被 AssistiveTouch聚焦 禁用用户交互的元素也是不可聚焦的 让我们来看一个例子 在本视图中有一个标签标题 包含有饮料信息的文本 “接受”和“取消”按钮元素 只有两个按钮可聚焦 标签和文本是不可聚焦的 让我们再来看看该视图的代码 点击手势附加于主VStack 表明您可以点击这个视图 以显示饮料的详细视图 不过 VStack内的静态元素 是不可聚焦的 除非显式声明 在这里 只有“接受”和 “取消”按钮是可聚焦的 那么 用户怎么知道他们能点击视图 以显示饮料的详细视图呢? 为提供更好的用户体验 我希望高亮显示饮料信息文本元素 以表明它是可交互的 可以进行点击操作 想要做到这一点 可以将 accessibilityRespondsToUser Interaction修饰符 设为“true” 应用该修饰符后 本视图将拥有三个可聚焦元素: 饮料信息文本 “接受”和“取消”按钮元素 现在 让我们进一步了解 AssistiveTouch光标框 正如您所见 对于AssistiveTouch 如果元素是可交互的 或者可执行某个操作 那么高亮显示被聚焦的元素很重要 AssistiveTouch 光标框 与元素的可点击区域相同 可点击区域小的元素 将拥有一个小的光标框 它可能会裁剪掉里面的内容 为了让内容显得更清晰 我们可以进行填充 并让边界与对象的形状相匹配 从而防止内容被裁剪 在本例中 AssistiveTouch 光标形成的小圆圈 突出显示了 一个带有省略图像的 NavigationLink 我们可以对此加以改进 方法是增加可点击区域的大小 这也会改变AssistiveTouch 光标框的大小 通过提供到contentShape 修饰符的路径 您可以修改元素的可点击区域 和光标框 对于这个NavigationLink 我将可点击区域的形状设定为 一个圆圈 其大小为 元素大小的1.5倍 结果AssistiveTouch 光标框比元素更大 在屏幕上更加容易看清 现在 让我们来看看 AssistiveTouch操作菜单 AssistiveTouch 操作菜单 将视图中的系统默认操作 和自定义操作 显示在操作菜单列表里 如果聚焦元素具有自定义操作 优先显示自定义操作 并出现在列表的最前面 因此更方便进行交互 如果元素是可调整的 将显示减量或增量操作 如果可访问性组元素包含自定义操作 那么在聚焦该组元素时 将显示这些操作 如果在元素中 已经为VoiceOver 增添了自定义操作 那就太好了! 这些操作将自动显示在 AssistiveTouch 操作菜单中 自定义操作将显示为一个图标 默认图像是 自定义操作名称的首字母 如果您希望为自定义 操作图标提供图像 您可以把带有图像的标签 accessibilityAction 修饰符 好了 今天的讲解到此结束 现在 您已经对 Apple Watch 的可访问性功能有了更好的了解 记得要让您的应用程序 支持Dynamic Type VoiceOver和 AssistiveTouch功能 您拥有使用这些API的工具 使您的 watchOS应用程序 可被所有人访问 感谢观看 请继续观看 其他WWDC课程! ♪
-
-
4:48 - Dynamic Type for PlantView
struct PlantView: View { @Binding var plant: Plant var body: some View { VStack(alignment: .leading) { Text(plant.name) .font(.title3) HStack() { PlantImage(plant: plant) PlantTaskList(plant: $plant) } PlantTaskButtons(plant: $plant) } } }
-
5:00 - Line limits for PlantTaskLabel
struct PlantTaskLabel: View { let task: PlantTask @Binding var plant: Plant var body: some View { HStack { Image(systemName: task.systemImageName) .imageScale(.small) Text(plant.stringForTask(task: task)) } .lineLimit(3) .font(.caption2) } }
-
5:48 - Alternate layouts for PlantContainerView
struct PlantContainerView: View { @Environment(\.sizeCategory) var sizeCategory @Binding var plant: Plant var body: some View { if sizeCategory < .extraExtraLarge { PlantViewHorizontal(plant: $plant) } else { PlantViewVertical(plant: $plant) } } }
-
8:56 - Element grouping for PlantCellView
struct PlantCellView: View { @EnvironmentObject var plantData: PlantData var plant: Plant var plantIndex: Int { plantData.plants.firstIndex(where: { $0.id == plant.id })! } var body: some View { NavigationLink(destination: PlantEditView(plant: plant).environmentObject(plantData)) { PlantContainerView(plant: $plantData.plants[plantIndex]) .padding() } } }
-
9:38 - Accessibility labels for PlantTaskLabel
struct PlantTaskLabel: View { let task: PlantTask @Binding var plant: Plant var body: some View { HStack { Image(systemName: task.systemImageName) .imageScale(.small) Text(plant.stringForTask(task: task)) .accessibilityLabel(plant.accessibilityStringForTask(task: task)) } .lineLimit(3) .font(.caption2) } }
-
10:03 - Accessibility labels for PlantButton
struct PlantButton: View { let task: PlantTask let action: () -> Void @State private var isTapped: Bool = false var body: some View { Button(action: { self.isTapped.toggle() DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.isTapped.toggle() } action() }) { Image(systemName: task.systemImageFillName) .foregroundColor(task.color) .scaleEffect(isTapped ? 1.5 : 1) .animation(nil, value: 0) .rotationEffect(.degrees(isTapped ? 360 : 0)) .animation(.spring(), value: 0) .imageScale(.large) } .buttonStyle(BorderedButtonStyle()) .accessibilityLabel("Log \(task.name)") } }
-
11:07 - Custom control accessibility for PlantTaskFrequency
struct PlantTaskFrequency: View { let task: PlantTask @Binding var plant: Plant let increment: () -> Void let decrement: () -> Void var value: Int { switch task { case .water: return plant.wateringFrequency case .fertilize: return plant.fertilizingFrequency default: return 0 } } var body: some View { Section(header: Text("\(task.name) frequency in days"), content: { CustomCounter(value: value, increment: increment, decrement: decrement) .accessibilityElement() .accessibilityAdjustableAction { direction in switch direction { case .increment: increment() case .decrement: decrement() default: break } } .accessibilityLabel("\(task.name) frequency") .accessibilityValue("\(value) days") }) } }
-
19:50 - Make static element focusable
struct FreeDrinkView: View { @State var didCancel = false @State var didAccept = false @State var showDetail = false var body: some View { VStack(spacing:10) { FreeDrinkTitleView() FreeDrinkInfoView() .accessibilityRespondsToUserInteraction(true) HStack { CancelButton(buttonTapped: $didCancel) AcceptButton(buttonTapped: $didAccept) } } .onTapGesture { showDetail.toggle() } .sheet(isPresented: $showDetail, onDismiss: dismiss) { DrinkDetailModalView() } } }
-
21:12 - AssistiveTouch cursor frame
struct DrinkView: View { var currentDrink:DrinkInfo var body: some View { HStack(alignment: .firstTextBaseline) { DrinkInfoView(drink:currentDrink) Spacer() NavigationLink(destination: EditView()) { Image(systemName: "ellipsis") .symbolVariant(.circle) } .contentShape(Circle().scale(1.5)) } } }
-
22:48 - AssistiveTouch Action Menu
PlantContainerView(plant: plant) .padding() .accessibilityElement(children: .combine) .accessibilityAction { // Edit action } label: { Label("Edit", systemImage: "ellipsis.circle") }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。