原文:Developing Inclusive Mobile Apps
协议:CC BY-NC-SA 4.0
六、iOS 可访问性模型
在我们深入研究可用的辅助功能之前,了解一下苹果的辅助功能在底层是如何工作的是很有用的。我们都熟悉我们的应用所呈现的可视化用户界面。它是你在我们设备的屏幕上看到的和与之互动的东西。我们的可视化用户界面由我们在代码中或通过界面构建器添加的控件、文本和图像组成。这些是用堆栈或约束组合在一起的,并以某种适当的颜色完成。但是你的应用有第二个用户界面,可访问性用户界面,或者可访问性树。
可访问性树
当您的 UIKit 屏幕出现时,iOS 会按照自然阅读顺序(在大多数语言中是从左上到右下)在屏幕上构建一个元素的可访问性树。这为用户选择的辅助技术创建了一个辅助用户界面(苹果称之为 AUI)。
由于 SwiftUI 的声明性,iOS 不必从视觉表示中推断我们的意图。相反,iOS 通过删除任何仅支持布局的元素,从 SwiftUI 代码构建 AUI。
AUI 上的每个可访问性元素包括关于可用元素的以下信息:标签、值、提示、特征和动作;我们将在这一章中一一介绍。AUI 不会为仅布局元素(如堆栈视图)创建可访问性树节点。但是 iOS 确实使用它们来帮助决定顺序元素应该出现在可访问性树中。
辅助功能协议和辅助功能附件修饰符
可访问性协议由 Apple 提供的所有 UIKit 元素实现。该协议负责处理关于每个元素的可访问元数据。苹果为每个视图设置了合理的默认值。这意味着您创建的任何自定义子类应该只需要调整,而不是完全实现该协议。
SwiftUI 的可访问性附件修饰符包含 UIKit 的大多数可访问性协议属性的等价物。为了避免重复,我在这里介绍了这两个方面。
可访问性元素
iOS 从视图中标记为可访问的任何元素创建可访问性用户界面,并忽略任何标记为不可访问的元素。苹果为他们提供的控件设置了一些合理的默认值。默认可访问的视图包括UILabel
、UITextField
、UIProgressView
以及任何扩展UIControl
的视图。其他视图,如UIImageView
和UIView
本身,默认情况下是不可访问的。
有时,使不可访问的元素对辅助技术可用可能是合适的(例如,参见第三章中关于图像替代文本的讨论),反之亦然,禁用通常可访问的视图(参见本章后面的“语义视图”一节了解更多信息)。标记为可访问的视图太多或不合适会给辅助技术用户带来噪音。太少意味着这些用户失去了上下文和功能。
为了定制哪些元素是可访问的,每个 UIKit 元素都有一个isAccessibleElement
属性(清单 6-1 )。这可以根据代码要求设置为 true 或 false。界面构建器在身份检查器的可访问性标题下有一个相应的复选框(图 6-1 )。SwitftUI 元素可以设置为 accessible,或者通过在视图中添加一个布尔值修饰符.accessibility(hidden: )
来设置(清单 6-2 )。
图 6-1
界面构建器中的辅助功能元素复选框
Image(systemName: "heart.fill")
.accessibility(hidden: false)
Listing 6-2Setting a view visible to accessibility in SwiftUI
- 1
- 2
- 3
- 4
- 5
detailLabel.isAccessibleElement = false
Listing 6-1Setting a view hidden to accessibility in UIKit
- 1
- 2
- 3
- 4
Caution
如果要创建具有交互性的自定义控件或视图,请避免创建 UIView 的子类。UIControl 通常是一个更好的选择,因为这个类已经设置了合理的可访问性默认值。如果您选择子类化 UIView,请确保与可访问性用户一起对此进行彻底的测试。
标签
当辅助功能元素获得焦点时,元素的辅助功能标签是 VoiceOver 读取的第一个字符串。它应该用于快速识别该元素是什么或做什么,而不一定是该元素的内容。请将此视为您的元素名称。默认情况下,大多数视图已经有一个可访问性标签。这是你的元素的文本值。如果您的视图没有文本值,例如带有图像的按钮,或者如果您的标签很长,您可能需要添加一个辅助功能标签。
理想情况下,标签应该用一个词来表达意思,比如“玩”或“喜欢”。你的标签应该大写,不要以句号结尾。不要包含元素的类型,因为这是多余的,会增加噪声。
可以在“身份检查器”选项卡下的界面生成器中设置元素的可访问性标签。或者可以用代码中的accessibilityLabel
属性来设置它(清单 6-3 )。在 SwiftUI 中,这可以使用.accessibility(label: "Send")
(列表 6-4 )来设置。
Image(systemName: "heart.fill")
.onTapGesture { ... }
.accessibility(label: "Like")
Listing 6-4Setting an accessibility label in SwiftUI
- 1
- 2
- 3
- 4
- 5
- 6
playButton.accessibilityLabel = "Play"
Listing 6-3Setting an accessibility label in UIKit
- 1
- 2
- 3
- 4
价值
可访问性值是元素的当前值。例如,这可以是在文本字段中输入的文本、滑块的当前数值或开关的当前状态。通常,您的可访问性值将由您的控件为您定义。例如,UISlider
子类总是将可访问性值设置为当前滑块值。
有时候您需要自己设置这个值。如果您正在创建一个自定义视图子类,您将需要确定您认为值的数据。如果您将子视图组合成一个具有不同值的语义视图,您将需要决定显示哪个值或者以什么顺序显示。如果多个元素有值,语义视图可能不是最佳选择。
您可以使用 UIKit 中的accessibilityValue
属性进行设置(清单 6-5 )。在 SwiftUI 中。可以应用accessibility(value: )
修改器(列表 6-6 )。两者都以字符串作为参数。
Image(systemName: "heart.fill")
.accessibility(value: "100")
Listing 6-6Setting an accessibility value in SwiftUI
- 1
- 2
- 3
- 4
- 5
playButton.accessibilityValue = "100"
Listing 6-5Setting an accessibility value in UIKit
- 1
- 2
- 3
- 4
使用 VoiceOver 时,将值添加到控制与将值添加到标签末尾的意思相同。因此,我经常看到在标签上添加值。虽然这对于 VoiceOver 来说很好,但对于盲文键盘和语音控制用户来说会有负面影响。盲文键盘在单独的寄存器中显示值,表明它们是可调的。语音控制将监听元素标签。在此基础上增加价值需要您的客户提供更高的准确性。
暗示
短暂停顿后,VoiceOver 会最后朗读元素的辅助功能提示。使用提示来给出关于执行这个元素的动作的结果的进一步的上下文,但是只有当这个结果从元素的可访问性标签中不是立即明显的时候。VoiceOver 用户可以停用提示,通常只是跳过它们。因此,最好假设您的客户不会听到这些声音。使用提示作为后备来提供额外的信息,而不是对你的 UI 至关重要的东西。
在他们关于编写良好的可访问性提示的指导中, 1 苹果建议你想象向一个朋友描述控件的动作。例如,您可以说“轻按“发送”按钮发送您的信息。”如果您的可访问性特征和标签已经正确设置,那么通知您的用户元素的名称、控件的类型以及它执行的操作是多余的。所以,如果我们把这些去掉,你的提示就是“发送你的信息”避免“发送你的信息”,因为这听起来像一个指示,而不是指导。提示应该以大写字母开头,以句号结尾。
元素可访问性提示是一个字符串属性,可以在身份检查器选项卡下的界面构建器中设置(图 6-2 )。它可以在代码中用accessibilityHint
属性进行配置(清单 6-7 )。在 SwiftUI 中,这可以使用.accessibility(hint: "Sends your message.")
来设置(清单 6-8 )。
图 6-2
在界面构建器中设置辅助功能提示和标签
Button("Send") { ... }
.accessibility(hint: "Sends your message.").
Listing 6-8Setting an accessibility hint in SwiftUI
- 1
- 2
- 3
- 4
- 5
sendButton.accessibilityHint = "Sends your message."
Listing 6-7Setting an accessibility hint in UIKit
- 1
- 2
- 3
- 4
特征
可访问性特征是 iOS 构建可访问性模型的基础。可访问性特征在任何 UIView 子类中都是可用的。苹果在他们提供的每一个视图和控件上设置默认值做得非常好。这意味着,在很大程度上,你不必改变苹果提供的东西。但这并不意味着不值得你花时间去检查这些默认特征是否合理。苹果并不确切知道你是如何使用你挑选的元素的。此外,特征可以随着控制状态的变化而变化。例如,如果您创建了一个秒表应用,当秒表运行时,时间标签将每秒或毫秒更新一次。这里使用特征updatesFrequently
是有意义的。假设用户按下 Lap 按钮,我们冻结这个时间,并在继续计时的UITableView
中添加一个新行。原来的标签现在已经把时间冻结了,不会变了。把这个标签标为updatesFrequently
已经没有意义了。相反,我们应该将标识符设置为staticText
。
可访问性特征可以在界面构建器中设置(图 6-3 ),方法是勾选(或取消勾选)您想要更改的特征旁边的方框。在代码中,这些可以使用任何UIView
上的accessibilityTraits
属性来设置(清单 6-9 到 6-14 )。accessibilityTraits
是一个位掩码,使它能够同时拥有多个特征。
图 6-3
在界面生成器中设置可访问性特征
.accessibility(removeTraits: .playsSound)
Listing 6-14Removing accessibility traits in SwiftUI; multiple traits can be removed with an array as above
- 1
- 2
- 3
- 4
.accessibility(addTraits: [.isHeader, . updatesFrequently])
Listing 6-13Adding multiple accessibility traits in SwiftUI
- 1
- 2
- 3
- 4
.accessibility(addTraits: .isHeader)
Listing 6-12Adding a single accessibility trait in SwiftUI
- 1
- 2
- 3
- 4
accessibilityTraits.remove(.selected)
Listing 6-11Removing a single trait in UIKit
- 1
- 2
- 3
- 4
accessibilityTraits.insert(.button)
Listing 6-10Adding a trait to existing traits in UIKit
- 1
- 2
- 3
- 4
accessibilityTraits = .none
Listing 6-9Setting a single trait removing all others in UIKit
- 1
- 2
- 3
- 4
特性在 UIKit、SwiftUI 和界面构建器中的名称和可用性略有不同(表 6-1 )。我们将依次讨论每一个特征及其影响。
表 6-1
可访问性特征名称
|
界面生成器
|
用户界面
|
斯威夫特伊
|
| — | — | — |
| 纽扣 | .button
| .isButton
|
| 图像 | .image
| .isImage
|
| 静态文字 | .staticText
| .isStaticText
|
| 搜索字段 | .searchField
| .isSearchField
|
| 播放声音 | .playsSound
| .playsSound
|
| 键盘键 | .keyboardKey
| .isKeyboardKey
|
| 汇总元素 | .summaryElement
| .isSummaryElement
|
| 启用用户交互 | .notEnabled
(IB 复选框的反码) | 没有直接特征,但是从控件的.enabled()
构造函数中推断出来 |
| 频繁更新 | .updatesFrequently
| .updatesFrequently
|
| 开始媒体会话 | .startsMediaSession
| .startsMediaSession
|
| 可调节的 | .adjustable
| --
|
| 允许直接互动 | .allowsDirectInteraction
| .allowsDirectInteraction
|
| 导致翻页 | .causesPageTurn
| .causesPageTurn
|
| 页眉 | .header
| .isHeader
|
| 环 | .link
| .isLink
|
| 挑选 | .selected
| .isSelected
|
| – | --
| .isModal
|
| – | .tabBar
| --
|
| (取消选择所有复选框) | .none
| --
|
没有人
这个元素没有特别的可访问性特征。
纽扣
这个元素是一个交互式按钮。这种特性会导致 VoiceOver 在阅读项目文本后发出“按钮”的声音。它还使元素对语音控制和开关控制可见。
环
导航到不同屏幕的内嵌链接,如网页中的链接。这种特性会导致 VoiceOver 在阅读项目文本后宣布“链接”。这个特性告诉语音控制和开关控制这个元素是交互的。
搜索字段
允许您的客户输入要搜索的字符串的文本字段。这一特征将该字段与标准文本字段区分开来。它提示用户,在这里输入文本会导致 UI 在其他地方更新。
图像
任何没有文本和动作的图像或视觉元素,也就是说,你不应该把这个特性应用到图像按钮上。请参见第三章中关于图像替代文本的讨论,了解何时使图像可访问。
挑选
当前选定的项,如选项卡或分段控件上的项。
播放声音
一旦被激活就会触发声音的元素。这将告诉 VoiceOver 在激活此元素时停止任何发声。
键盘键
用作键盘上的键的项,例如,如果您正在实现自定义输入控件。这允许与按键直接交互,以及 VoiceOver 焦点。
静态文字
在视图的整个生命周期中不变的文本。
汇总元素
摘要元素特征描述了在屏幕上提供信息概览的区域。这方面最好的例子就是苹果内置的天气 app(图 6-4 )。打开一个位置时,VoiceOver 会聚焦在标记为摘要元素的顶部区域。然后,VoiceOver 会朗读所选位置的当前天气状况摘要。
图 6-4
苹果的天气应用,带有突出顶部摘要元素的 VoiceOver
未启用
此项目已被禁用,如果被激活,将不会发生任何事情。请注意,Interface Builder 以相反的方式显示此复选框,启用用户交互。在 SwiftUI 中,这种特性没有直接的对等物。SwiftUI 从视图的.disabled()
构造函数中确定这个属性。
经常更新
这种特性适用于更新标签或值过于频繁而无法发布UIAccessibilityLayoutChangedNotification
通知的元素。这告诉用户选择的辅助技术以适当的时间间隔轮询该元素的值和标签变化。这方面的一个用例是时间显示。
开始媒体会话
用于激活后开始播放或录制媒体的元素。一旦元素被激活,这种特性会导致 VoiceOver 语音暂停,从而防止媒体会话被中断。
可调节的
将这一特性用于滑块或选择器等元素,用户可以从一系列值中进行选择。确保你的控件也实现了accessibilityIncrement()
和accessibilityDecrement()
。我们将在本章的后面讨论这些。这个特质在 SwiftUI 中没有对应的特质。
允许直接互动
允许直接交互告诉 VoiceOver 此视图不应偏离标准 UIKit 触摸控制。
假设你创建了一个音乐 app,提供钢琴键盘给用户弹奏(图 6-5 )。使用划动按键和双击的 VoiceOver 范例不会产生太多的曲调。允许直接交互仅对此控制停用 VoiceOver 手势导航。这允许您的用户通过轻按按键来弹奏键盘,而无需为 UI 的其余部分停用 VoiceOver。不恰当地使用这一特性会给 VoiceOver 用户带来更糟糕的体验。
图 6-5
Apple 的 Garage Band 应用,带有突出显示键盘视图的 VoiceOver。这种观点具有允许直接互动的特点。允许在键盘上进行多点触控交互,同时保持键盘上方控件的 VoiceOver 导航
导致翻页
这个特征向屏幕读者表明,这个内容代表一组页面中的一页,比如电子书。
这个特性导致屏幕阅读器在阅读完内容后立即调用这个视图上的accessibilityScroll()
方法。然后,屏幕阅读器将开始读取任何新内容。如果调用此函数后内容没有变化,读取将停止。
页眉
导航栏的标题或任何分隔内容的大文本标题元素。
通过垂直滑动或调整转子控制,VoiceOver 用户可以利用这一特性浏览您的内容。这有助于确定哪些内容与他们的需求相关,而不必扫描整个屏幕。
选项卡栏
这一特征表明视图不是直接交互的,而是包含可以交互的选项卡按钮。任何具有该特征的元素都必须为isAccessibilityElement
返回 false。
情态的
这个特性只有 SwiftUI 才有。它是 UIKit 的accessibilityViewIsModal
属性的 SwiftUI 版本。这一特性导致辅助技术忽略屏幕上任何其他视图的内容,只允许该视图的子视图访问。
语言
元素的辅助功能语言属性允许我们指定希望 VoiceOver 为该元素使用的语言规则(清单 6-15 )。它是一个遵循 BCP 47 规范 2 的字符串值,告诉 VoiceOver 应该以用户系统语言之外的语言读取该元素的可访问性标签、值和提示。如果不需要替代语言,则不需要设置该值。
// es-419 is the code for Latin American Spanish.
spanishGreeting.accessibilityLanguage = "es-419".
Listing 6-15Setting an element’s accessibility language
- 1
- 2
- 3
- 4
- 5
隐藏的元素
隐藏的元素,accessibilityElementsHidden
(列表 6-16 ),是一个只有 UIKit 的布尔值。它告诉辅助技术任何子视图都是不可访问的。当一个视图被另一个动画视图部分遮挡时,可以使用此选项。
contentView.accessibilityElementsHidden = true
Listing 6-16Setting children of contentView hidden to accessibility
- 1
- 2
- 3
- 4
视图是模态的
accessibilityViewIsModal
(清单 6-17 )是一个只有 UIKit 才有的布尔属性。它会导致辅助技术忽略屏幕上除此视图之外的任何视图。辅助技术只能访问设置了它的视图的子视图。在 SwiftUI 中,您可以通过应用.isModal
特征来做同样的事情。
customAlert.accessibilityViewIsModal = true
Listing 6-17Setting the customAlert view modal for assistive technologies in UIKit
- 1
- 2
- 3
- 4
元素顺序
辅助技术从左上到右下呈现屏幕。大多数时候,这对我们的界面是正确的。但有时这使得阅读屏幕不合逻辑。如果你的界面中有交错排列的元素,或者你是垂直显示元素,就会发生这种情况。
以 App Store 为例。在应用或游戏的列表页面上,应用图标的正下方是一个栏,显示了该应用的一些详细信息:下面是评级数量的星级评定,下面是应用类别的排名,下面是标题为“年龄”的年龄评定。如果 VoiceOver 在没有对我们的 AUI 进行任何修改的情况下阅读这篇文章,结果将是“4 星”。第一。12+.14K 收视率。策略。年龄。”这些信息杂乱无章,毫无意义。
相反,VoiceOver 读“4 颗星”会更有意义。14K 收视率。”为了做到这一点,我们的子视图可以告诉可访问性,它的元素是否应该以特定的顺序访问。我们创建的每个子视图都向 UIAccessibility 返回一个accessibilityElements
数组。iOS 通常会为我们生成这个,但是我们可以有更多的控制权。在这个例子中(清单 6-18 ,我们使用一个UIView
子类作为三个 header 和三个 detail 元素的容器。我们对每个元素都有一个@IBOutlet
引用;然后我们告诉UIAccessibility
我们希望这些元素被遍历的顺序。我们没有添加到这个数组中的任何元素都将被辅助技术忽略。
class DetailView: UIView {
@IBOutlet weak var header1: UILabel!
@IBOutlet weak var header2: UILabel!
@IBOutlet weak var header3: UILabel!
@IBOutlet weak var detail1: UILabel!
@IBOutlet weak var detail2: UILabel!
@IBOutlet weak var detail3: UILabel!
override var accessibilityElements: [Any]? {
set{}
get{
return [header1!,
detail1!,
header2!,
detail2!,
header3!,
detail3!]
}
}
}
Listing 6-18Informing UIAccessibility of the order we’d like our elements traversed in a subview
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
逃跑
有辅助技术的导航通常比没有辅助技术的慢,原因有几个。有时,当把屏幕作为一个整体来看时,可能会更清晰的上下文会丢失。为此,苹果提供了一些全局快捷方式。其中之一是神奇的水龙头;我们将在本书后面的画外音部分讨论这一点。另一个支持 VoiceOver 和开关控制的快捷键是 Escape(清单 6-19 )。
图 6-6
开关控制的退出命令
通过开关控制,当选择任何元素时,可以从动作菜单中选择退出选项(图 6-6 )。使用 VoiceOver 时,可以用两个手指在屏幕上画一个 Z 形来进行转义。如果你使用的是标准的UINavigationController,
,你可以免费获得这个行为。您可能会发现,如果您要呈现定制的模态元素,就需要使用它。
override func accessibilityPerformEscape() -> Bool {
// Dismiss the current view.
return true // return false if the view can’t be dismissed.
}
Listing 6-19Supporting the accessibility Escape command
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
自定义操作
加速辅助功能导航并为辅助功能用户创造更好体验的一个好方法是使用自定义辅助功能操作。自定义动作允许我们向只能通过辅助技术访问的元素添加动作。在开关控制中,当控制被聚焦时,这些动作显示在控制选项中(图 6-7 )。使用 VoiceOver,当元素被选择时,控制将显示“可用操作”。然后可以通过垂直滑动来循环这些动作。
图 6-7
“10 个旅行者”的自定义可访问性操作
为了确定任何可用的自定义动作,可访问性 API 将查询视图的accessibilityCustomActions
属性(清单 6-20 )。这将返回任何可用操作的数组。通过初始化一个UIAccessibilityCustomAction
对象来创建新的动作。如果成功,您的操作应该返回 true。
override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
set {}
get {
return [UIAccessibilityCustomAction(name: "10 travelers") { customAction in
self.slider.value = 10
return true
}]
}
}
Listing 6-20Returning a new custom accessibility action for a view subclass
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
控制焦点
在某些情况下,通过辅助技术了解元素何时获得或失去焦点是很有用的,比如说,如果您需要更改视图的状态或想要添加辅助突出显示。可访问性 API 为您提供了两个函数来接收这些事件的更新。accessibilityElementDidBecomeFocused()
和accessibilityElementDidLoseFocus()
(列表 6-21 )。这些事件由 VoiceOver 和开关控制触发。
override func accessibilityElementDidBecomeFocused() {
// this element became focused, change status as needed.
}
override func accessibilityElementDidLoseFocus() {
// this element lost focused, reset the elements status.
}
Listing 6-21Listening for changes in accessibility focus for an element
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
框架和激活点
accessibilityFrame
(列表 6-22 )标记了当聚焦于一个元素时,开关控制等辅助技术将用来绘制高亮的界限。这通常是视图的框架,但是如果您确实需要扩展框架,这个属性允许您这样做。
myView.accessibilityFrame = CGRect(x: myView.frame.origin.x - 10,
y: myView.frame.origin.y – 10,
width: myView.frame.size.width + 20,
height: myView.frame.size.height + 20)
Listing 6-22Setting a custom accessibility frame
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如果你调整你的元素的框架,你也应该设置激活点。这是屏幕上的点,辅助技术将在激活控件时合成一个点击。您可以通过向accessibilityActivationPoint
传递一个CGPoint
来实现这一点。
增量和减量
一些辅助技术改变了我们与屏幕上元素的交互方式。画外音和开关控制就是两个例子;我们将在后面的章节中更多地讨论他们如何以及为什么这样做。但这意味着我们需要一种新的控制模式,而不仅仅是简单的点击激活。滑块就是一个很好的例子。UIKit
的UISlider
控制实现了两个功能,accessibilityIncrement()
和accessibilityDecrement()
。
图 6-8
开关控制的增量和减量控制
任何使用.adjustable
可访问性特征的视图都必须覆盖这两个函数。当用户的辅助技术执行递增或递减操作时,将调用这些函数。这可以是点击开关控制上的增量开关(图 6-8 )或者用 VoiceOver 垂直滑动。然而,iOS 将UISlider
控件呈现给可访问性的方式存在问题,所以让我们来看看如何解决这个问题。无论何时使用UISlider
控件,你都应该在自己的应用中做类似的事情。如果你选择这样做的话,这也会给你知识来创建你自己的可访问的可调节控件。一般来说,我会一直推荐使用UIKit
提供的控件。
Accessible Slider
克隆这本书的 git repo,在第六章下,寻找滑块练习。这里我们将创建一个可访问的滑块控件。
这个例子是一个应用的一部分,它允许客户预订去我们太阳系不同地方的假期。这个例子中包含的屏幕是火星列表的细节。在屏幕中央,我们有一个滑块控件,让我们的客户输入他们为多少旅行者预订。当您增加或减少滑块时,我们会更新上面的标签以显示所选的数字。
如果您使用 VoiceOver 来尝试此控制,以下是读取的内容。”旅行者:0。”然后我们滑动到滑块。”百分之零,可调。”如果我们用 VoiceOver 向上滑动来执行辅助功能增量操作,答案是“10%”
这里有几个我们可以解决的可访问性问题。虽然太空是一个危险的地方,但我不认为我们的任何客户会愿意带着一定比例的旅行者,相反,他们更喜欢整个人。所以我们应该改变画外音的回应。此外,盲人用户如何知道这个滑块的用途?之前有一个“旅行者”的标签,但是我们怎么知道这个滑块控制这个值呢?
我们可以通过将这两个元素组合在一起来解决这两个问题。在本例中,我已经将这两个元素放在 UIView 子类中。当滑块改变时,子类处理标签更新。在您自己的项目中,将滑块和标签添加到视图中,如果您还没有这样做的话。
首先,我们需要告诉 accessibility API 一些关于容器视图的信息。它需要知道视图是可访问的并且是可调整的。让我们在 init 中这样做。
required init?(coder: NSCoder) {
super.init(coder: coder)
isAccessibilityElement = true
accessibilityTraits.update(with: .adjustable)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
现在,如果你在这个屏幕上运行 VoiceOver,你的容器视图将被聚焦,你会被告知它是可调的。但是没有标签,如果我们试图调整它,什么也不会发生。所以现在我们只是让体验变得更糟。让我们添加一个可访问性标签和值。
override var accessibilityLabel: String? {
set{}
get{
return "Travelers"
}
}
override var accessibilityValue: String? {
set{}
get{
return "(Int(slider.value))"
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
现在辅助功能用户可以知道控件是做什么用的,但是他们仍然没有办法控制它。这就是我们使用accessibilityIncrement()
和accessibilityDecrement()
函数的地方。在这个例子中,我们只想改变我们的滑块,我们已经写的代码将处理其余的。所以我们需要做的就是将这些交互传递给滑块。如果您正在创建自己的自定义控件,您需要在这里添加适当的逻辑来处理每个交互。
override func accessibilityDecrement() {
slider.accessibilityDecrement()
}
override func accessibilityIncrement() {
slider.accessibilityIncrement()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
我们还需要更新一个行为。使用原来的滑块时,每次我们更改滑块时,VoiceOver 都会告诉我们滑块的百分比值。这对我们没什么帮助,因为滑块控件的值是浮点数,所以 VoiceOver 总是给我们读一个百分比值。但那种行为比我们现在听到的要好得多,那根本不算什么。因此,让我们模仿这种行为,但使它更适合我们的使用。我们已经在sliderValueChanged()
函数中更新了代码中的可视标签。这似乎也是更新 VoiceOver 客户的最佳机会。找到这个函数,我们在现有代码后添加一行。
@IBAction
func sliderValueChanged() {
...
UIAccessibility.post(notification: .announcement,
argument: "(rounded)")
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
现在,当我们在这个控件上运行 VoiceOver 时,我们减少了滑动次数,增加了清晰度。以这种方式一起使用可访问性工具来创建复合可访问性视图是一种称为语义视图的技术。这是一个强大的工具,如果你想让最易访问的界面成为可能,这是必不可少的。
语义视图
如果你想让你的应用的可访问性更上一层楼,创建语义视图是必须的。语义视图是由多个元素组成的视图,这些元素组合在一起是为了便于访问,因为它们一起具有意义或语义。这项技术是关于把我们在本章中提到的所有可访问的用户界面工具整合在一起。目标是为易访问性客户创建一个更容易理解和更快导航的界面。
当你使用辅助技术导航时,你会注意到 iOS 中经常使用的语义视图。从 iOS 的“文件”应用中查看图 6-9 中的表格单元格。这里有几条信息:文件名、日期、大小和下载按钮。如果这些是单独呈现的,那就要用画外音扫四下。如果我们激活了下载按钮,将不会清楚哪个文件会被下载。相反,您可以看到 VoiceOver 将单元格高亮显示为一个元素。
图 6-9
VoiceOver 将单元格及其内容高亮显示为一个语义视图
有时候 iOS 可以聪明到在一些UITableViewCell
设计上为你创建语义视图。大多数情况下,我们需要手动创建它们。有几种方法可以构建语义视图。
自定义视图
我们在前面的辅助滑块练习中介绍了这种技术。在那个练习中,我们使用了一个UIView
作为容器。我们为视图创建了一个定制的UIView
子类。然后,这个视图处理可访问性交互,并代表我们的控件将信息反馈给可访问性 API。这是我的首选方法,因为这个组件可以在你的应用中重用。iOS 也给了你很多免费使用标准的UIAccessibility
和UIView
功能。
基本框架
创建语义视图的另一种方式是调整视图的accessibilityFrame
来包含其他元素。然后,通过将isAccessibleElement
设置为 false,您可以从可访问性树中移除重叠的元素。根据需要将任何其他元素的可访问性属性传递给带有扩展框架的视图。
这项技术要求您仔细计算框架,并在布局改变时重新计算它们。例如,假设您的客户更改了字体大小或旋转了屏幕。使用这种方法最适合简单的视图,比如没有任何交互的视图。如果你正在展开的视图是一个控件,你需要设置accessibilityActivationPoint
来确保辅助技术激活你的控件,而不是你展开的框架的中心。
Semantic View Frame
图 6-10
用 VoiceOver 分别访问这两个标签是不必要的冗长
使用这种技术的一个例子是垂直堆叠的元素。如果我们想显示一个星级和下面的评论数量,将这些作为一个元素分组是有意义的。首先,让我们在我们的viewDidLoad
回调中添加几行代码来设置标签并隐藏不需要的视图。
class ViewController: UIViewController {
@IBOutlet weak var rating: UIButton!
@IBOutlet weak var responses: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
responses.isAccessibilityElement = false
rating.accessibilityLabel = "(stars) stars from (respondents) ratings"
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
第二步是计算框架。我们需要在viewDidLayoutSubviews
中这样做,以确保如果视图改变,iOS 重新计算可访问性框架。
图 6-11
我们新的语义视图只需一次滑动,更有意义
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let ratingX = Float(rating.frame.offsetBy(dx: 0,
dy: 0).origin.x)
let responsesX = Float(responses.frame.offsetBy(dx: 0,
dy: 0).origin.x)
// Find the left-most item.
let x = CGFloat(fmin(ratingX,
responsesX))
let y = rating.frame.origin.y
let ratingWidth = Float(rating.frame.offsetBy(dx: 0,
dy: 0).size.width)
let responsesWidth = Float(responses.frame.offsetBy(dx: 0,
dy: 0).size.width)
// Match the width of the widest element.
let width = CGFloat(fmax(ratingWidth,
responsesWidth))
// Calculate the height of both elements plus the padding between each.
let height = rating.frame.size.height + padding + responses.frame.size.height
rating.accessibilityFrame = CGRect(x: x,
y: y,
width: width,
height: height)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
如果现在充当语义视图的元素是一个按钮,那么还需要一个步骤。记得告诉辅助技术人员accessibilityActivationPoint
在按钮元素上,而不是在框架的中心。在viewDidLayoutSubviews
中这样做,这样每次你的屏幕改变时都会被计算。
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
...
rating.accessibilityActivationPoint = CGPoint(x: rating.frame.midX,
y: rating.frame.midY)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
SwiftUI 斯塔克斯
SwiftUI 中引人注目的可访问性变化之一是使用单个修饰符创建语义视图的机制。在 SwiftUI 中,你的大部分布局都是使用堆栈来完成的,可以是HStack
、VStack
或ZStack
。这些堆栈中的每一个都可以包含许多不同种类的视图。标准行为是堆栈按照视图在堆栈中出现的顺序将所有这些视图呈现给可访问性。使用.accessibilityElement(children: )
修改器,我们可以改变这种行为。
如果我们通过了。combine
对于accessibilityElement
修饰语,辅助技术并不单独关注每一个元素。相反,它们关注于堆栈,并且堆栈的每个子级的可访问性属性都被传递给堆栈。
通常情况下,使用.ignore
值可以给客户带来更好的体验。可以预见,Ignore 会忽略堆栈中的所有视图。Ignore 也使堆栈成为可聚焦的。但是堆栈本身没有可访问性属性,所以我们需要添加任何标签、特征、提示和动作。如果我们想创建一个类似于前面评级视图的视图,我们的代码应该如清单 6-23 所示。
VStack (alignment: .leading, spacing: 10) {
Button("⭐⭐⭐⭐") { self.tappedRatings() }
.font(.largeTitle)
Text("1K ratings")
.font(.subheadline)
}
.padding()
.accessibilityElement(children: .ignore)
.accessibility(label: Text("(stars) stars from (respondents) ratings"))
.accessibility(addTraits: .isButton)
.accessibilityAction { self.tappedRatings() }
Listing 6-23SwiftUI semantic view
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
Twitter 示例
Twitter 提供了一个很好的案例研究,说明语义视图在哪里产生了巨大的影响。尝试在启用开关控制和 VoiceOver 的情况下导航 iOS Twitter 应用,并记录阅读推文时的体验。如果你有其他的 Twitter 客户端,试一试,看看它们的不同之处。
当你在视觉上浏览时间线时,你不会查看头像、Twitter 名称、句柄、推文、链接、喜欢、转发等等。你在看推特。但是每条推特都是由一大堆其他东西组成的。这里有一条来自 Accessibility London meetup 的推文(图 6-12);VoiceOver 正在高亮显示该推文。如果 VoiceOver 关注每一个元素,这将使浏览 Twitter 变得非常令人沮丧。让我们看看如何创建类似的东西。
图 6-12
由画外音聚焦的推文
Semantic
Cell
从本书的 GitHub repo 中,打开语义单元示例。这里我们有一个用于社交媒体帖子的自定义表格单元格。我们有用户的头像、姓名和帖子内容。下面是评论、喜欢和分享的数量。每个按钮都可以让我们评论、喜欢或分享。头像也是用户资料的一个按钮。
启动 VoiceOver 并查看为该单元格读取的内容。iOS 已经为我们做了很多工作,将所有单元格的内容组合在一起。滑动到下一个元素,你会看到这篇文章上的一个按钮,而不是时间线中的下一篇文章。这将使得浏览数百个帖子非常耗时。
首先,让我们从可访问性标签中删除用户名。这对 VoiceOver 用户来说并不重要,而且会增加噪音。我们通过设置自己的可访问性标签来做到这一点。
class SemanticCell: UITableViewCell {
...
var model: TimelineEntry! {
didSet{
...
accessibilityLabel = "(model.user.name). (model.content)"
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
接下来,我们应该从可访问性界面中移除按钮。这将意味着帖子之间的导航更加顺畅。
class SemanticCell: UITableViewCell {
...
var model: TimelineEntry! {
didSet{
...
comment.isAccessibilityElement = false
share.isAccessibilityElement = false
like.isAccessibilityElement = false
avatar.isAccessibilityElement = false
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
很好,除了现在我们的可访问性用户不能访问这些功能。让我们将它们作为辅助功能操作添加回去。出于本练习的目的,我们实际上不会在动作中添加任何代码。在您自己的应用中,您会希望在这里添加一些代码。
class SemanticCell: UITableViewCell {
...
var model: TimelineEntry! {
didSet{
...
let likeAction = UIAccessibilityCustomAction(name: "Like, (model.likes)") {_ in
return true
}
let shareAction = UIAccessibilityCustomAction(name: "Share, (model.shares)") {_ in
return true
}
let commentAction = UIAccessibilityCustomAction(name: "Comment, (model.comments)") {_ in
return true
}
let profileAction = UIAccessibilityCustomAction(name: "view (model.user.name) profile") {_ in
return true
}
accessibilityCustomActions = [commentAction, likeAction, shareAction, profileAction]
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
摘要
-
控制辅助技术如何与您的应用交互的系统由所有 iOS 辅助技术共享。
-
苹果称之为无障碍用户界面;您可能熟悉同一个概念,称为可访问性树。
-
iOS 为我们做了很多开箱即用的事情;坚持使用标准的 iOS 视图控件,你的应用的可访问性会很好。测试你的应用,最好是有真正的可访问性用户,找出你需要调整的地方。
-
语义视图是将你的应用的可访问性提升到一个新水平的重要技术。
既然你已经知道了 iOS 可访问性是如何工作的;让我们来看看 iOS 为您的客户提供的一些辅助工具。我们将了解您如何最好地支持他们,以及如果您发现您的客户启用了其中一项功能,您可能需要考虑哪些事项。
Footnotes 1
https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/iPhoneAccessibility/Making_Application_Accessible/Making_Application_Accessible.html#//apple_ref/doc/uid/TP40008785-CH102-SW6
2
www.rfc-editor.org/rfc/bcp/bcp47.txt
七、iOS 辅助功能——概述
苹果的人机界面指南 1 (通常被称为 HIG)是任何为 iOS 开发移动应用的人的必读之作——不仅仅是设计师。HIG 阐述了苹果如何让 UIKit 对你这个应用开发者变得灵活,同时对你的用户变得清晰而有意义。
在考虑如何为你的 iOS 应用引入可访问性时,关于可访问性的章节 2 是一个起点。HIG 为您提供了使用 iOS 内置可访问性注意事项的最佳概览。让你的应用与本指南保持一致(不仅仅是可访问性部分)将有助于你的用户在你的应用中有宾至如归的感觉,因为苹果设计的许多系统模式将移植到你的应用和其他应用中。此外,遵循本指南并确保高水平的可访问性意味着您的应用更有可能出现在 App Store 上。UIKit 和 SwiftUI 都提供了强大的定制选项,允许您在保持可访问性的同时,为您的应用提供独特的外观和感觉。
人们使用 苹果的辅助功能 ,例如降低透明度、画外音和增加文本大小,以适合他们的方式个性化他们与设备的交互方式。可访问的应用通过设计支持这种个性化,并为每个人提供出色的用户体验,无论他们的能力或他们如何使用他们的设备。
—苹果人机界面指南:可访问性
我向苹果的可访问性团队询问了他们从客户那里收到的关于第三方应用可访问性的反馈。他们告诉我:
我们收到的关于第三方应用可访问性的两个最常见的反馈涉及元素标签和 VoiceOver 的导航顺序。此外,调色板选择、黑暗模式支持和媒体字幕都是很好的实践。
—苹果无障碍团队 3
我们在第六章的“可访问性协议”一节中介绍了可访问性标签。在这一章的后面,我们还包括了改进导航顺序的技术,比如语义视图。有关选择颜色的可访问性规则,请参见第三章中的可区分指南。有关包含字幕的指南,请参见第十章。
在这一章中,我们将介绍 iOS 系统范围的辅助功能,为什么有人会启用它们,以及启用它们会如何影响你的应用。这不是辅助功能设置的完整列表;从最终用户的角度来看,更多关于可用内容的详细信息,请参阅 Apple 网站的可访问性部分。相反,本章关注的是可能会改变你的应用外观或工作方式的可访问性设置,或者可能需要你添加代码或做出决定来最好地支持它们的设置。
如果您使用推荐的系统 APIs 动态类型、可访问性特征等。,您将免费获得其中的许多功能。SwiftUI 的可访问性支持远远好于 UIKit,原因在前一章中已经提到,但是仍然需要一些定制。如果你通过 web 视图或其他跨平台系统创建一个非本地应用,辅助工具通常会模仿你正在编译的系统。最终,就像任何跨平台的东西一样,特性会有所不同,而且可能会受到限制。虽然苹果会尽可能地为你做,但有些设置需要你查询 iOS 的UIAccessibility
框架进行设置,并自行决定如何处理。
从 iOS 13 开始,辅助功能菜单现在是系统设置中的顶级菜单(图 7-1 ),分为四个标题,涵盖了该技术旨在帮助的障碍类别——一般、视觉、听觉以及身体和运动。在 iOS 12 和更早的版本中,这些功能可以在设备的“系统设置”中的“通用➤辅助功能”下找到。
图 7-1
iOS 13 中的辅助功能设置(右)位于设备设置的顶层(左)
该菜单中的每个辅助功能选项都提供了启用该选项的简短描述。每个选项一旦被触发,就会立即启用并呈现在整个系统中。启用后,引导式访问需要一个额外的步骤来激活该功能。
花些时间浏览一下这份菜单;启用每个选项,浏览您的应用,查看每个选项如何改变您的应用的外观和行为。没有任何设置是破坏性的,可以立即禁用。但是,某些设置(如 VoiceOver)会改变设备的功能。因此,首先阅读一些关于这些特性的内容是值得的,至少这样你就知道如何在完成后禁用它们。该菜单完全是关于可定制性的,因此您可以找到您想要在您的个人设备上保持启用的选项。
一般特征
苹果将其无障碍考虑分为四类——认知、运动、视觉和听觉(图 7-2 )。出于这个原因,我将可访问性设置分成了四个相似的类别。运动、视觉和听觉反映了苹果的类别。虽然没有具体的设置来帮助那些有认知障碍的人,但许多其他设置也会帮助那些属于这一类别的人。在第一部分,我们将介绍 iOS 的一些常规设置和功能。
图 7-2
苹果在 2018 年 WWDC 车展上展示了其四类无障碍考虑因素
辅助功能快捷方式
我首先提到这一点,因为这将允许您快速简便地在 iOS 上启用许多辅助功能。因此,这是测试您的应用与许多 iOS 辅助工具协同工作的最佳方式。启用这个简单的特性将鼓励您将这些特性作为开发和测试工作流的一个常规部分来激活。重要的是,这也是禁用这些功能的最简单方法,一旦您启用了改变设备工作方式的功能,这可以省去很多麻烦。
Tip
在尝试本章中的任何内容之前,请启用辅助功能快捷方式。
进入设置➤辅助功能➤辅助功能快捷方式,在设备的辅助功能设置中启用辅助功能快捷方式。您会发现辅助功能快捷方式是列表中的最后一项。我强烈建议启用列表中的每一项,这样你就可以在需要的时候快速地尝试一下。
图 7-3
启用此列表中的每个可用选项,以便以后轻松访问该功能
该菜单上可用的选项有辅助触摸、经典反色、智能反色、滤色器、放大镜、减少白点、开关控制、画外音、缩放、引导访问和语音控制(图 7-3 )。但是,具体选项可能会因设备的设置而异。我建议在您的测试设备上启用这些功能;我们将在本章中更详细地介绍这些特性。
图 7-4
按下主屏幕或睡眠按钮三次后激活的辅助功能快捷方式
然后,您可以在需要时激活辅助功能快捷方式,方法是在旧设备上连按三次侧边按钮或主屏幕按钮。这将显示一个模式菜单(图 7-4 ),您可以在其中激活或取消激活您从前面列表中选择的功能。我强烈建议启用这个快捷方式,因为它不会影响你对 iPhone 的正常使用,但当你想查看一些东西时,它会让你轻松地访问辅助工具。
控制中心
切换辅助功能的第二种快速访问方式是通过控制中心,尽管这种方式比较有限。从屏幕顶部向下滑动,或者在带有 home 按钮的设备上从下向上滑动,将显示一串按钮,提供对常用设备控制的快速访问(图 7-5 )。显示的控件可通过您设备的设置应用进行自定义。跟随➤控制中心定制控制。此处可用的辅助功能控件有黑暗模式、引导式访问和文本大小。还有一个选项,可以用我们在上面看到的设置中选择的相同选项来切换辅助功能快捷方式。配有助听器的用户也可以在这里控制它们。
图 7-5
底部一行带有辅助功能控件的控制中心。l–R 文本大小、辅助功能快捷方式、黑暗模式和引导式访问
引导访问
引导式访问允许设备仅锁定到当前应用,并禁用某些应用和系统功能。引导式访问使设备成为独立的信息亭设备,通常用于零售场所,在这些场所,您可能不希望让公众不受限制地访问设备。然而,引导式访问主要是为了帮助有各种不同需求的人:那些有学习困难的人很容易被新事物弄糊涂,或者不能完全理解特定行为的后果。患有运动障碍的人有时会导致意外输入,然后无法将设备恢复到他们可以使用的状态。患有注意力缺陷障碍焦虑症的人会被太多的刺激压垮。在所有这些情况下,限制可用的选项是可取的。
需要在设备的辅助功能设置中启用引导式访问。启用后,可通过按下侧面或主屏幕按钮三次的辅助功能快捷方式激活(图 7-6 )。引导式访问针对每个应用会话启用,必须禁用才能退出应用。
图 7-6
设置引导式访问
您可以通过检查isGuidedAccessEnabled
来检测引导式访问是否被激活,并在该设置改变时从guidedAccessStatusDidChangeNotification
(列表 7-1 )接收更新。在您的应用中,您可以使用此设置的状态来决定是否锁定单个功能,如设置或破坏性操作。
import UIKit
class MyViewController: UIViewController {
var guidedAccessStatus: Bool {
get{
return UIAccessibility.isGuidedAccessEnabled
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(guidedAccessChanged), name: UIAccessibility.guidedAccessStatusDidChangeNotification, object: nil)
}
@objc
func guidedAccessChanged() {
// check guidedAccessStatus for current status.
// Hide features as appropriate.
}
}
Listing 7-1Registering for notifications in guided access status
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
利用这一点的更好方法是在应用启动时实现UIGuidedAccessRestrictionDelegate
。此委托允许您设置自定义操作,当客户在您的应用中启用引导式访问时,可以根据请求启用或禁用这些操作。例如,您可以为“设置”或“删除项目”添加限制这可以由设置引导访问的人来配置,以使引导访问用户能够根据他们的偏好来定制设置,但是不允许他们删除任何项目。
首先,我们需要为用户可能想要禁用的每个特性创建唯一的字符串。将这些创建为 enum(清单 7-2 )允许我们保持这种类型的安全,并提供我们需要的额外数据。
enum Restriction: String, CaseIterable {
case settings = "com.myCompany.myApp.restriction.settings"
case delete = "com.myCompany.myApp.restriction.delete"
}
Listing 7-2Providing unique strings for Guided Access features
- 1
- 2
- 3
- 4
- 5
- 6
- 7
接下来,我们需要在 iOS 上向客户显示人类可读的字符串——一个用作按钮标签的短字符串,然后是一个较长的描述性字符串。让我们扩展清单 7-3 中的枚举,将这些值与唯一的字符串关联起来。
extension Restriction {
var title: String {
switch self {
case .settings:
return "Settings"
case .delete:
return "Delete"
}
}
var detail: String {
switch self {
case .settings:
return "Allow changing settings"
case .delete:
return "Allow permanent deletion of items"
}
}
}
Listing 7-3Associating human-readable strings with our restrictions enum
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
现在我们需要通过符合UIGuidedAccessRestrictionDelegate
(清单 7-4 )在我们的应用委托中向 iOS 提供这些字符串。我们需要遵循两个协议方法和一个变量。guidedAccessRestrictionIdentifiers
变量是一组唯一的字符串,供 iOS 和我们的应用用来识别功能。然后我们有两个函数,textForGuidedAccessRestriction
和detailTextForGuidedAccessRestriction
,在这里我们提供我们人类可读的字符串。为了让我们的应用委托更整洁,让我们在扩展中这样做。
extension AppDelegate: UIGuidedAccessRestrictionDelegate {
var guidedAccessRestrictionIdentifiers: [String]? {
return Restriction.allCases.map { $0.rawValue }
}
func textForGuidedAccessRestriction(withIdentifier restrictionIdentifier: String) -> String? {
return Restriction(rawValue: restrictionIdentifier)?.title
}
func detailTextForGuidedAccessRestriction(withIdentifier restrictionIdentifier: String) -> String? {
return Restriction(rawValue: restrictionIdentifier)?.detail
}
}
Listing 7-4Providing our Guided Access strings to iOS
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
最后,当用户改变某个特性的引导访问状态时,我们需要处理 iOS 的回调(清单 7-5 )。为此,我们需要在我们的扩展中符合另一个委托函数,guidedAccessRestriction(withIdentifier restrictionIdentifier: didChange:
)。这个函数为我们提供了一个.allow
或.deny
的newRestrictionState
枚举值。
extension AppDelegate: UIGuidedAccessRestrictionDelegate {
...
func guidedAccessRestriction(withIdentifier restrictionIdentifier: String,
didChange newRestrictionState: UIAccessibility.GuidedAccessRestrictionState) {
switch restrictionIdentifier {
case Restriction.settings.rawValue:
if newRestrictionState == .deny {
// remove settings feature
} else {
// add settings feature
}
case Restriction.delete.rawValue:
if newRestrictionState == .deny {
// remove delete feature
} else {
// add delete feature
}
default:
preconditionFailure()
}
}
}
Listing 7-5Handling user changes in Guided Access feature status
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
此外,您的应用可以随时查询UIAccessibility
API 上的guidedAccessRestrictionState(forIdentifier: String)
函数,以确定限制的状态(清单 7-6 )。这可以用来决定是否拒绝一个动作,或者更好的是,完全隐藏一个选项。
import UIKit
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let deleteFeatureState = UIAccessibility.guidedAccessRestrictionState(forIdentifier: Restriction.delete.rawValue)
switch deleteFeatureState {
case .allow:
// enable the delete feature
case .deny:
// disable the delete feature
@unknown default:
preconditionFailure()
}
}
}
Listing 7-6Detecting the current status of a Guided Access restriction
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
本地化
整个 iOS 都在构建本地化。虽然将你的应用翻译成不同的语言可能会很复杂和微妙,但支持这一点的编码并不复杂。你可能觉得你的应用不需要本地化,因为你的业务目前只在一个国家可用,但这并没有反映我们的全球社会。在任何市场中,都有相当一部分人不会把市场的主要语言作为他们的第一语言。此外,从代码中删除硬编码的字符串通常有利于代码健康。
您可能会发现您的应用已经设置为开始本地化,即使您还没有本地化任何内容。但是要检查,请转到您项目的设置,并滚动到底部(图 7-7 )。在这里,您可以找到本地化部分。确保“使用基础国际化”被选中,您就可以开始了。
图 7-7
Xcode 项目的本地化设置
在这里,您可以按+按钮添加新的语言本地化,并开始翻译您的应用。若要将应用的字符串发送给翻译人员,请前往 Xcode 的编辑器菜单,然后选取“导出以进行本地化”。这将创建翻译需要的所有文件。当你得到你的翻译回来,访问同一个菜单,并选择进口本地化。但是,在此之前,您需要对您的应用进行一些更改。
视图
任何 xib 或故事板文件都可以本地化,这不仅仅意味着字符串。
使用 autolayout 对于本地化是必不可少的,正如它对于动态类型一样。不可能保证字符串的可视长度适用于每个本地化版本。许多语言比英语占用更多的空间。在德语中,英语的 12 个字符本地化为 13 个字符的 Lokalisierung,而在简体中文中,则为 3 个字符本土化。您可以使用伪语言来测试这一点;我们将在第十一章中对此进行介绍。
也考虑到一些语言,例如阿拉伯语,是从右向左书写的。iOS 可以很好地翻转你的 UI,但这是基于 autolayout 约束的。因此,未能完全解决这些问题可能会产生一些奇怪的影响。最后,确保你的文本对齐正确。如果这确实是你每次体验的意图,只使用左对齐文本。对于大多数用途,您应该使用自然对齐。这在大多数语言中从左到右显示文本,但在需要时会切换到从右到左。
当通过按下图 5-3 中的+按钮创建新的本地化时,Xcode 会显示一个对话框,询问您希望对项目中的每个视图文件执行什么操作。您有两个选项–一个新的视图文件和一个本地化的字符串文件。对于大多数用途,本地化字符串文件将是首选选项。关于如何工作的更多信息,请参见下一节关于字符串的内容。
更有效的方法是创建一个新的视图文件。这将复制现有文件,但允许您对视图进行修改,而不仅限于文本值。您可以使用这些文件来修改视图对于比您的开发语言短得多或长得多的语言的工作方式,或者那些以不同方向编写的语言的工作方式。请注意,您对一个视图文件所做的任何更改都不会反映在任何其他视图文件中,这意味着如果您支持五种本地化,那么您必须更改五个界面构建器文件。好消息是不同的本地化可以有不同的本地化方法。例如,可以用字符串文件支持四种本地化,而用新的界面构建器文件只支持一种本地化。
创建新的视图文件时,默认情况下这些文件不会本地化。如果您决定本地化,请在导航器中选择文件,并打开文件检查器。按下本地化按钮(图 7-8 )生成该视图的本地化版本。
图 7-8
Xcode 文件检查器的“本地化”按钮
用线串
并非你的应用的所有字符串都会嵌入到故事板中。对于这些,您需要创建一个 Localizable.strings 文件。访问文件➤新➤文件,并选择了一个字符串文件。称之为 Localizable.strings
该文件是应用中使用的字符串的键值对的集合。密钥可以是您希望的任何字符串格式,但通常最好在应用中保持格式一致。试着一眼就看出这是一把钥匙,而不是一根绳子。并尝试阐明字符串的使用位置和用途。出于这些原因,我更喜欢首先使用所有大写字母命名屏幕,然后是字符串的用途,用句点分隔的格式(清单 7-7 )。用分号结束每一行,即使你使用的是 Swift。
"DETAIL_PAGE.TITLE" = "Listing Detail";
Listing 7-7A localized value of “Listing Detail” used as the title for the app’s detail page
- 1
- 2
- 3
- 4
要在代码中使用这些字符串,使用清单 7-8 中的NSLocalizedString
。这将字符串键作为参数和注释。Xcode 向您的翻译人员提供注释,指导他们如何最好地翻译意思。请注意,如果找不到您的本地化字符串,密钥就是显示给客户的字符串。因此,您可以选择传递一个 value 属性来提供默认值。
pageTitle.text = NSLocalizedString("DETAIL_PAGE.TITLE",
value: "Listing Detail",
comment: "Header for the listing page")
Listing 7-8Setting the pageTitle UILabel’s text using a localized string in code
- 1
- 2
- 3
- 4
- 5
- 6
Localizing a Project
让我们本地化一个现有的项目。如果你还没有,克隆这本书的 GitHub repo。在 Xcode 中打开练习 7-1。如果尚未选择,请从项目导航器顶部选择项目文件。然后在编辑器窗格中选择项目。
图 7-9
查找演示项目的本地化设置
您会注意到已经有两个本地化版本(图 7-9 )。基本本地化-默认;英语——我的发展语言。让我们继续添加另一个。我将使用 Google Translate 获取德语值;你也可以选择你喜欢的语言。
警告切勿使用自动化服务翻译您的应用;始终使用专业的翻译服务。
如图 7-10 所示,按下本地化下方的+按钮并选择德语(de)。
图 7-10
更改您的方案的语言
我们将使用字符串文件,因此在下一个屏幕上(图 7-11 ,确保两个故事板文件都选择了可本地化字符串选项。
图 7-11
选择所有要本地化的文件,然后选择“可本地化的字符串”
在左侧的项目导航器中,两个文件旁边都出现了一个显示指示器。打开 Main.storyboard 旁边的文件,注意有两个子文件(图 7-12 ),一个基础本地化脚本和一个标记为 German 的新字符串文件。
图 7-12
项目导航器显示了我们本地化的故事板
首先,让我们看看故事板文件,看看我们有什么字符串。该应用用于预订太空度假(图 7-13 ),每个详细页面都为我们提供了关于我们可以访问的太空目的地的信息,并附有一张照片。将出现在这个视图中的大部分文本将根据列表而变化,所以我们没有将它们包含在这个故事板文件中。我们希望跨目的地重用的唯一字符串是“Destination”
图 7-13
我们的假日列表应用显示了我们需要本地化的字符串
打开新的字符串文件。Xcode 已经为故事板中唯一的字符串生成了一个键值对。在这里,将目的地一词替换为“Ziel”
其余的字符串是在代码中动态设置的,因此,我们需要一个本地化的字符串文件。转到文件➤新➤文件,并选择一个字符串文件。将此命名为 Localizable.strings。我们需要将三个字符串从 ViewController.swift 移动到该文件中。使用合适的键添加这些字符串,如清单 7-9 中的示例。
"DETAIL_PAGE.MARS.HEADING" = "Mars";
Listing 7-9An entry in a localizable strings file
- 1
- 2
- 3
- 4
现在我们需要告诉视图控制器使用这些新的本地化字符串。类似于清单 7-10 中的内容。
pageTitle?.text = NSLocalizedString("DETAIL_PAGE.MARS.HEADING",
value: "Mars",
comment: "Mars planet name")
Listing 7-10Accessing a localized string in code
- 1
- 2
- 3
- 4
- 5
- 6
对于另外两个字符串,请务必遵循上面的两个步骤,为每个字符串指定自己的标识符。一旦你完成了这些,我们需要为我们的字符串文件创建一个德语版本。在项目导航器中选择文件,并打开文件检查器。按下“本地化…”按钮(图 7-8 )。Xcode 然后会询问现有文件属于哪个本地化版本。选择英语并按本地化。回到文件检查器,你会注意到本地化按钮已经消失了,取而代之的是一个活动本地化列表,在英语旁边有一个复选标记。查德语。将为您创建一个内容与原始文件相同的新文件。在这里,我们可以开始添加新的德语本地化字符串。
德语中的火星就是火星。因此,让我们删除标题的键值对,因为我们将只使用默认值。将副标题值替换为“地球”,并替换描述。我将让您在这里创建自己的翻译。
要在不更改设备或模拟器设置的情况下用德语运行您的应用,请编辑您目标的方案(图 7-14 )。在运行➤选项下,有一个应用语言的下拉菜单(图 7-15 )。
图 7-15
使用应用语言选项更改应用的运行语言
图 7-14
编辑你的应用方案以更改运行语言
把这个换成德语,关上窗户。下次运行时,您的应用将显示您的新德语翻译。
摘要
-
阅读苹果的人机界面指南。这是让你的应用在 iOS 上对所有用户都有宾至如归的感觉的最后一句话。遵循苹果在这里提出的建议,你就更有可能让你的应用出现在 App Store 上,而不太可能被拒绝应用审查。
-
在继续阅读图书之前启用辅助功能快捷方式,即使在阅读完毕后也保持启用状态。它可以让你快速访问流行的可访问性工具,并有助于无障碍测试。
-
为本地化准备您的应用是一个很好的实践,因为它从您的代码中删除了硬编码的字符串,并使将来更改它们变得更容易。如果你决定本地化你的应用,大量的工作已经完成。
-
即使你的应用只在一个市场可用,你仍然可以通过本地化你的应用来扩大你的市场。
在接下来的三章中,我们将更深入地探讨 Apple 为有特殊残疾或需求的人而创建的辅助功能。下一章将涵盖最广泛的功能;视觉考虑。
Footnotes 1
https://developer.apple.com/design/human-interface-guidelines/
2
https://developer.apple.com/design/human-interface-guidelines/accessibility/overview/introduction/
3
给作者发邮件。
4
www.apple.com/uk/accessibility
八、iOS 辅助功能——视觉
以下功能是 Apple 主要为帮助有视觉障碍的人而设计的。这些功能帮助盲人和低视力用户,包括远视、低视力、色觉障碍、失明等。有些配置对患有认知障碍、多动症、识字率低的人,或者喜欢文本大一点或颜色柔和一点的人也有帮助。有些人启用灰度彩色滤镜只是因为它看起来很酷。视觉因素构成了目前 iOS 上可用的最大一组辅助功能。
画外音
开发者最熟悉的 iOS 辅助功能是 VoiceOver。VoiceOver 是苹果的内置屏幕阅读器,在所有基于屏幕的平台上都有,可以与你的应用配合使用,而不需要开发者启用它的使用。VoiceOver 不仅仅是一个基本的屏幕阅读器;它还可以作为弱视用户的导航工具,使他们不仅能够知道屏幕上有什么文本,还能知道他们可以使用什么按钮或动作。iOS 还具有更基本的屏幕阅读器,仅用于阅读内容;这种屏幕阅读器在 iOS 中被称为语音内容,我们将在本章后面的“朗读选择”和“朗读屏幕”部分对此进行介绍
Caution
在您阅读“使用 VoiceOver 导航”之前,请不要启用 VoiceOver
VoiceOver 将改变你的应用的功能,因为你的用户将采用一系列的滑动来以自然的方向导航你的应用(在英语中是从左上角到右下角)。在屏幕上绘制一个边界框,为当前选择的元素增加一个视觉亮点(图 8-1 )。选择后,VoiceOver 会按以下顺序向用户朗读元素信息:辅助功能标签➤辅助功能值➤辅助功能特征➤辅助功能提示。这四个值中的每一个都是为你确定的,作为你的应用的辅助功能用户界面的一部分,在第六章中有所涉及,如果 iOS 不能确定理想的,或者实际上任何一个值,你都可以定制。
图 8-1
VoiceOver 选择放大镜。双击屏幕上的任意位置来激活此控件
虽然 VoiceOver 会为您做大量的工作,但创建与 VoiceOver 不理想兼容的界面是很容易的。常见的陷阱包括缺少或不正确的可访问性值、标签或特征;以非逻辑顺序访问的元素;VoiceOver 无法通过滑动访问或离开元素的陷阱;和 VoiceOver 与您的用户界面不同步。由于这些原因,在创建屏幕时使用 VoiceOver 检查屏幕是非常重要的,以确保您可以按预期导航。也请记住,VoiceOver 用户不会像视力正常的人那样拥有全屏环境;它们唯一的上下文是当前选中的元素,所以每个元素必须有自己的意义。第六章中涉及的语义视图是这个上下文问题的一个很好的解决方案。
您可以通过检查isVoiceOverRunning
来检查 VoiceOver 当前是否正在运行,并根据清单 8-1 中的示例通过监听voiceOverStatusDidChangeNotification
来确定此状态是否发生变化。
import UIKit
class MyViewController: UIViewController {
var voiceOverStatus: Bool {
get{
return UIAccessibility.isVoiceOverRunning }
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(voiceOverChanged), UIAccessibility.voiceOverStatusDidChangeNotification, object: nil)
}
@objc
func voiceOverChanged() {
// check voiceOverStatus for the current status.
}
}
Listing 8-1Detecting VoiceOver status and changes
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
屏幕更新
UIKit 中一个常见的 VoiceOver 陷阱是在 iOS 将屏幕推至视图层次后更新屏幕上的元素。VoiceOver 并不总是知道您的视图已经更改,因此它可能不会找到新元素或可能会阅读旧内容。在 SwiftUI 中,这不再是一个考虑因素,但是对于 UIKit,您应该总是向UIAccessibility
发布通知。收到此通知后,可访问性 API 将重新构建可访问的用户界面,以将更改考虑在内。如果你改变了一个元素,发布layoutChanged
通知(列表 8-3 ,或者,对于屏幕的更大区域,发布screenChanged
(列表 8-2 )。对于这些通知中的每一个,您都可以包含一个参数。此参数可以是 VoiceOver 要宣布的字符串,也可以是 VoiceOver 应该关注的元素。
func deleteItem(item: Item) {
// handle removing the item from your screen.
...
// VoiceOver announces "One item removed" and the accessibility user interface is recreated to reflect this.
// VoiceOver focus is unchanged, unless the focus was on the removed element.
UIAccessibility.post(notification: .layoutChanged, argument: "One item removed")
}
Listing 8-3Posting VoiceOver Change Notification for a screen layout change
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
func updateSearchResults(results: Results) {
// update your table view.
...
let firstCell = tableView.cellForRow(at: IndexPath(row: 0, section: 0))
// Focuses VoiceOver on the first cell.
UIAccessibility.post(notification: .screenChanged, argument: firstCell)
}
Listing 8-2Posting VoiceOver Change Notification for a major screen change
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
也可以在 UI 不变的情况下,直接向 VoiceOver 发送公告来响应事件。例如,在清单 8-4 中,一个通常不会打断用户的警告或错误,比如你可以选择在哪里显示一个 toast。
func reachabilityFailed() {
// display a toast informing of poor network connectivity.
...
// VoiceOver announces "Warning: Poor network connectivity detected".
// No changes are made to the accessibility user interface or VoiceOver focus.
UIAccessibility.post(notification: .announcement,
argument: "Warning: Poor network connectivity detected")
}
Listing 8-4Posting a VoiceOver announcement
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
以这种方式发布通知将会中断任何当前的发言。所以如果你只在绝对必要的时候使用这种技术是最好的。另一种方法是,我们可以告诉 VoiceOver,我们希望它仅在使用属性字符串(清单 8-5 )完成当前发言后发出通知。
func reachabilityFailed() {
// display a toast informing of poor network connectivity.
...
let announcement = NSAttributedString(string: "Warning: Poor network connectivity detected", attributes: [.accessibilitySpeechQueueAnnouncement: true])
UIAccessibility.post(notification: .announcement, argument: announcement)
Listing 8-5Posting a queued announcement
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
作为回报,UIAccessibility
在阅读完您的公告后,会向notificationCenter
发送一个名为UIAccessibility.announcementDidFinishNotification
的通知。
属性化可访问性字符串
就像可视化显示富文本的属性化字符串一样,我们也可以向可访问性字符串添加属性。这些属性不是控制颜色和下划线之类的东西,而是控制说话时是否包含标点之类的东西。组成可访问性用户界面的每个标签、提示和值都可以有属性。这些属性是accessibilityAttributedLabel
、accessibilityAttributedHint
和accessibilityAttributedValue
。我们还可以将属性字符串传递给我们上面提到的 VoiceOver 通知。我们可以添加的属性如下。
投
accessibilitySpeechPitch
(列表 8-6 )键允许我们提供一个从 0.0 到 2.0 的NSNumber
浮点值。此值调整 VoiceOver 用来朗读此文本的音高。1.0 代表用户选择的画外音音高,< 1.0 降低音高,> 1.0 提高音高。这对于强调一段话很有用。
let attributedString = NSMutableAttributedString(string: "This is the best app on the App Store!")
// Always localize your strings and perform proper range calculations. I'm hard-coding values here for brevity.
let range = NSRange(location: 12, length: 4)
attributedString.addAttributes([.accessibilitySpeechPitch: 1.5], range: range)
appDescription?.accessibilityAttributedLabel = attributedString
Listing 8-6Adjusting VoiceOver pitch for emphasis
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
语言
这个键accessibilitySpeechLanguage
(列表 8-7 ,允许我们指定一个 BCP 47 1 语言键来定义用于发音字符串的语言规则。这可以用于为非本地单词提供更准确的发音。
spanishGreeting?.accessibilityAttributedLabel = NSAttributedString(string: "Hola!", attributes: [.accessibilitySpeechLanguage: "es-419"])
Listing 8-7Setting VoiceOver language for an foreign language word
- 1
- 2
- 3
- 4
拼出
单独读出字符串中的每个字符。从我自己的测试来看,这个在 iOS 13 上用大写的单词玩的并不好。
当您的应用包含账号或电话号码等数字时,accessibilitySpeechSpellOut
(列表 8-8 )键非常有用。当 VoiceOver 遇到数字时,它会决定是将这些数字读为整数还是数字;根据我的经验,这个决定并不总是正确的。应用此属性意味着 VoiceOver 将始终单独朗读每个插图。
let attributedString = NSMutableAttributedString(string: "Your account number is 12345678")
// Always localize your strings and perform proper range calculations. I'm hard-coding values here for brevity.
let range = NSRange(location: 23, length: 8)
attributedString.addAttributes([.accessibilitySpeechSpellOut: true], range: range)
accountNumber?.accessibilityAttributedLabel = attributedString
Listing 8-8Telling VoiceOver to read an account number as digits
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
注音符号
VoiceOver 并不总是每个单词都有正确的发音,例如,如果这个单词是一个品牌名称,就更是如此。使用国际音标,或国际音标,我们可以指定画外音如何发音。这里的关键是accessibilitySpeechIPANotation
(列表 8-9 )。
brandName.accessibilityAttributedLabel = NSMutableAttributedString(string: "[air-bee-an-bee]", attributes: [.accessibilitySpeechIPANotation: true])
Listing 8-9Using IPA notation to specify pronunciation of AirBnB
- 1
- 2
- 3
- 4
标点
如果您的应用包含代码或其他一些标点符号很重要的文本,您可以使用accessibilitySpeechPunctuation
(列表 8-10 )来强制 VoiceOver 读出每个标点符号。
helloWorld.accessibilityAttributedLabel = NSMutableAttributedString(string: "print("Hello, World!")", attributes: [.accessibilitySpeechPunctuation: true])
Listing 8-10Requesting VoiceOver to announce punctuation for code
- 1
- 2
- 3
- 4
神奇水龙头
Magic Tap 是辅助功能用户快速访问屏幕上重要操作的一种方式。例如,在计时器应用中,Magic Tap 启动或停止计时器。VoiceOver 用户通过用两个手指双击屏幕上的任何位置来执行神奇的点击。您可以在视图控制器或子视图中添加一个神奇的 Tap。将它添加到一个子视图意味着只有当这个视图被聚焦时才执行这个动作。魔术 Tap 的标准用法是将它添加到视图控制器中;这样,无论焦点中的元素是什么,操作都将被执行。
通过覆盖accessibilityPerformMagicTap()
函数来支持魔术点击手势(清单 8-11 )。然后你应该调用你认为你的屏幕的主要目的。然后返回 true 或 false,如果这不可能的话。
override func accessibilityPerformMagicTap() -> Bool {
// perform your screen's main action
return true // return false if the action failed
}
Listing 8-11Supporting Magic Tap
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Navigating With Voiceover
VoiceOver 不需要先启用;一旦您在“设置”中打开此选项,VoiceOver 就会被激活。VoiceOver 改变了 iPhone 的导航方式,因此在不知道如何先禁用它的情况下,不要打开它并四处轻敲,这一点很重要。很多次,当我在苹果经销商处工作时,客户会带来几天没用的 iPhones,因为闲置的拇指让他们启用了 VoiceOver,但他们无法再次关闭它。我甚至听说有人为了让手机失效而彻底擦拭手机。
Voiceover 会按照用户语言设置的自然顺序(英语为从左上方到右下方)检测辅助元素,并一次高亮显示一个元素。单次点击不再激活项目,就像你在点击按钮时期望的那样,而是点击导致元素被读取。屏幕也可以通过滑动来导航,因为元素可能不总是可见或足够大,以至于弱视的人无法准确点击。因此,向右滑动会移至右下方的下一个元素;向左滑动将会向左上方导航。该元素将被激活并被读取。
激活一个元素——拨动开关,点击按钮等。–手势现在变成双击。无论您点击屏幕上的哪个位置,也无论您点击时手指下面是什么,按钮都会对双击做出响应。这对于有视觉障碍的人来说是有益的,因为他们可能很难确定点击目标的准确位置——因此消除这一要求意味着更精确的控制激活。它也方便了纱窗的使用。屏幕幕布完全遮住了 iPhone 的屏幕,所以在其他人看来,这款手机就像睡着了一样。除了节省电池,这还为盲人和视力受损的用户提供了额外的隐私。除了大声朗读他们的个人内容,盲人用户可能不知道屏幕的全部内容。这最终可能会无意中向周围的人显示敏感的个人信息。
图 8-2
VoiceOver 转子控制选择标题
转子控制(图 8-2 )可用于配置辅助 VoiceOver 手势的行为方式。垂直滑动而不是水平滑动将执行转子上选择的功能。最常见的用途之一是标题选项,当在屏幕上垂直滑动时,该功能将跳过内容,只阅读带有标题可访问性特征的元素。这有助于浏览屏幕内容,而不必浏览每个元素。其他转子控制可让您通过辅助操作、容器来导航,控制语速,或通过朗读单个字符或单词来拆分文本。
一款云视频会议软件
屏幕缩放对那些视野狭窄的人很有用。但是,如果您的动态文本支持不够,或者 voiceover 不清楚屏幕上的内容,您可能会发现其他人也在使用它。当您执行用户测试时,如果您发现有视觉障碍的参与者在特定的屏幕上启动缩放,您可能会发现这是因为您需要在这里进行其他的可访问性改进。
图 8-3
在窗口模式下启用缩放
变焦可用于两种模式中的一种:首先是窗口变焦,取景器出现在屏幕上,放大下面的内容(图 8-3 )。这类似于在屏幕上使用放大镜,但不会显示您在现实世界中使用放大镜看到的底层 RGB 像素。其次,全屏缩放,即整个屏幕被缩放,不再适合整个设备的显示。
这是让它成为一些视障用户的第二选择的原因——这两种模式都可以有效地将显示给客户的内容减少到全屏显示的四分之一左右。这意味着四分之三的屏幕内容不再可见,最明显的是非自然一侧的内容(右为英语)。要使用缩放,你需要在屏幕上移动手指来跟随内容,这使得元素很容易被错过,并且可以看到的内容失去了上下文。内容也会变得像素化和模糊,因此更难确定。
缩放还具有 HUD(图 8-4 )功能,可以轻松控制缩放区域——全屏或窗口,轻松定位缩放区域,能够更改缩放级别,并可以选择仅向缩放区域添加颜色滤镜。对于那些发现彩色滤镜有助于他们查看内容,但不要求滤镜总是处于启用状态的人来说,最终选择是最佳选择。
图 8-4
缩放 HUD 选项
默认情况下,屏幕缩放将跟随屏幕上的焦点,这意味着如果您的客户正在填写表单,缩放将随着光标的移动而移动到下一个文本字段。有时,特别是如果您使用自定义控件,这可能不会像您希望的那样无缝地发生。在这种情况下,您可以使用zoomFocusChanged(zoomType: .insertionPoint, toFrame: myCGRect, in: myUIView)
(列表 8-12 )通知 Zoom 应该将焦点改变到您屏幕的某个区域,其中myCGRect
是要聚焦的区域。
import UIKit
Class MyViewController: UIViewController {
@IBOutlet private var myCustomView: CustomView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIAccessibility.zoomFocusChanged(zoomType: .insertionPoint, toFrame: myCustomView.frame, in: view)
}
}
Listing 8-12Moving Zoom focus to a view on viewDidAppear
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
要在屏幕上移动缩放区域,需要使用三指滑动,这意味着如果你在应用中使用三指手势,这些手势不会被 zoom 使用,也不会传递到你的应用。与大多数辅助功能不同,Apple 没有为您的应用提供属性来确定缩放是否已启用,以及在需要时更改行为。相反,你可以使用屏幕上的registerGestureConflictWithZoom()
来提示 iOS 呈现系统警告(图 8-5 ),通知你的客户缩放可能会与你的应用的功能冲突;此警报允许您的用户禁用缩放。
图 8-5
当调用registerGestureConflictWithZoom()
且用户启用缩放时,出现缩放手势冲突对话框
粗体文本
粗体文本如其在锡上所言(图 8-6 )。如果你正在使用UIFontTextStyles
,你的文本将免费获得这个。如果你已经选择不使用苹果的文本样式,你应该听听isBoldTextEnabled
和boldTextStatusDidChangeNotification
(列表 8-13 )并切换到粗体字体或根据要求添加粗体属性。您可能还想考虑切换任何资源或增加边框的粗细,以使元素更加突出。
图 8-6
带有标准文本(左)和粗体文本(右)的 iOS 文本样式
import UIKit
class MyViewController: UIViewController {
var boldTextStatus: Bool {
get{
return UIAccessibility.isBoldTextEnabled
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(boldTextChanged), name: UIAccessibility.boldTextStatusDidChangeNotification, object: nil)
}
@objc
func boldTextChanged() {
// check boldTextStatus for current status.
// switch to bold fonts or assets as appropriate
}
}
Listing 8-13Detecting Bold Text status changes
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
较大的文本或动态类型
更大的文本是我们作为开发人员称之为动态类型的友好的可读名称。动态类型允许用户将文本大小调整到更适合他们使用设备的大小(图 8-7 )。从最佳辅助功能的角度来看,这不仅仅是关于需要更大的文本来阅读屏幕的人,而是关于定制。此功能允许文本大小低于默认大小,从而无需滚动即可在屏幕上显示更多内容。范围从正文大小为 14pt 的 xSmall (extra small)到正文大小为 53 的 AX5 (accessibility size 5)。参见表 8-1 了解正文字体大小的完整范围。
表 8-1
动态文字大小
|
动态字体大小
|
正文大小(磅)
|
| — | — |
| xSmall | Fourteen |
| 小 | Fifteen |
| 中等 | Sixteen |
| 大(默认) | Seventeen |
| xLarge | Nineteen |
| xx 大 | Twenty-one |
| 大 | Twenty-three |
| ax 1 | Twenty-eight |
| ax 2 | Thirty-three |
| ax 3 | Forty |
| ax4 | Forty-seven |
| AX5 | Fifty-three |
作为 HIG 的一部分,可提供与文本样式匹配的各种动态文本大小。 2
动态字体与 iOS 的内置文本样式配合使用时效果最佳。这些样式告诉 iOS 和动态类型系统特定文本的语义用法是什么,这意味着您的所有文本都按照苹果推荐的间隔按比例缩放。这里显示了不同大小设置的文本样式的完整选项。
图 8-7
标准尺寸(左)、最小尺寸(中)和最大辅助功能尺寸(右)的动态文字样式
作为一般规则,你的所有文本应该使用这些内置文本样式之一,清单 8-14 展示了如何在 UIKit 中实现这一点。在 SwiftUI 中,所有文本都支持动态类型,默认样式为body
。可以使用.font()
修饰符(列表 8-15 )选择其他样式,并传递一个动态类型样式。传递一个定义好的大小,比如.system(size: 17)
,会禁用动态类型,防止缩放,所以应该避免。
// For body style, no font modifier is required.
Text("Example text").font(.headline)
Listing 8-15Creating labels with Dynamic Type in SwiftUI
- 1
- 2
- 3
- 4
- 5
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
Listing 8-14Creating labels with Dynamic Type in UIKit
- 1
- 2
- 3
- 4
- 5
- 6
要在界面构建器中启用动态类型(图 8-8 ,打开属性检查器,并在字体属性框中选择“T”图标。然后单击出现的新字体下拉菜单,选择您选择的文本样式。最后,选中下面标有“自动调整字体”的框
图 8-8
在界面构建器中选择动态文字样式
自定义样式
如果您的设计需要一种不同于 iOS 默认字体 San Francisco 的字体,该怎么办?或者,如果您希望您的标准正文文本大小大于 17 呢?我们可以使用 iOS’ UIFontMetrics
(清单 8-16 )来做到这一点。我不会在这里介绍给你的应用添加自定义字体,但是苹果在他们的开发者文档中提供了一个简单的指南。准备好自定义字体后,在代码中创建一个字体实例,当文本设置为标准大小时,使用您希望的字体和大小。创建一个使用该字体的文本样式的UIFontMetrics
实例,然后将你的自定义字体实例传递给你的UIFontMetrics
实例。
let font = UIFont(name: myCustomFontName, size: defaultSize)
let bodyMetrics = UIFontMetrics(forTextStyle: .body)
label.font = bodyMetrics.scaledFont(for: font)
label.adjustsFontForContentSizeCategory = true
Listing 8-16Creating a dynamic text style with a custom font
- 1
- 2
- 3
- 4
- 5
- 6
- 7
以这种方式创建文本样式可确保您尊重客户选择的文本大小。此外,Apple 会为您配置行距、段落间距和其他指标,确保您的文本始终可读。
按钮形状
按钮形状通过添加不同的轮廓、背景或带下划线的文本(图 8-9 )来帮助使按钮点击目标更加可见。对于没有视觉障碍的人来说,按钮形状也使得确定屏幕上的哪些区域可以进行交互变得更加简单。如果您以任何方式自定义了按钮,有时可能会发现文本或按钮形状没有对齐。有必要启用这些功能来检查你的应用看起来是否正常,按钮标签是否可读。如果你使用一个没有从UIButton
子类化的元素作为按钮,你不会得到这种行为。
图 8-9
启用按钮形状的后退导航按钮
开/关标签
对于 iOS 的开关或切换控制,苹果的设计只使用颜色来指示用户开关是开还是关,这意味着任何有视觉障碍的人都很难区分颜色,很难理解这种控制。因此,苹果公司提供了一种设置来为开关添加标签——I 在打开位置,O 在关闭位置,如图 8-10 所示。
图 8-10
启用开/关标签的开关控制
当创建你自己的定制控件时,你也应该考虑这一点,如果你选择默认不使用标签,听听isOnOffSwitchLabelsEnabled
和onOffSwitchLabelsDidChangeNotification
(列表 8-17 )来决定什么时候添加额外的信息。
import UIKit
class MyViewController: UIViewController {
var switchLabelStatus: Bool {
get{
return UIAccessibility.isOnOffSwitchLabelsEnabled
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(switchLabelsChanged), name: UIAccessibility.onOffSwitchLabelsDidChangeNotification, object: nil)
}
@objc
func switchLabelsChanged() {
// check switchLabelStatus for current status
// add on/off labels to your controls as needed.
}
}
Listing 8-17Detecting changes in switch label status
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
降低透明度
降低透明度是一些弱视用户的基本特征;对于那些视力已经模糊的人来说,在原本透明的背景上添加模糊会使区分前景和背景变得很困难。如果在构建时没有定义背景,透明度也会导致对比度低于可接受的值。
这一点最明显的用途是跳板上的文件夹(图 8-11 )。这些都是一个很好的例子,说明了如何使一个可访问性特性具有可比较的体验,而不是二等的体验。禁用此功能后,文件夹的背景为浅灰色透明。轻敲文件夹,透明的灰色文件夹 squircle 后面的背景是一个未打印的模糊跳板。启用这个设置,当访问一个文件夹时,跳板内容被删除,保留一个暗淡的跳板背景图像。文件夹呈现较暗的灰色外观,没有透明度,但是灰色背景颜色暗示了其背后的跳板背景图像的颜色。
图 8-11
跳板文件夹(左)。透明度降低的跳板文件夹(右)
默认情况下,不会像许多文本调整一样为您启用降低透明度,因为 Apple 无法根据您的 UI 设计明确决定如何降低透明度。如果你在你的设计中使用透明,你必须决定是否以及如何响应这个设置。如果您的透明图层位于一种纯色之上,并且您可以保证这种颜色在构建时不会改变,则可能根本不需要响应。在所有其他情况下,应该做出一些妥协。一个简单的决定是用纯色代替透明色;一个更好的选择可能是制作一个类似的体验,比如苹果的跳板文件夹,其中使用了背景图像的色调。
为了确定是否响应这个设置,使用 iOS 的 Accessibility API,访问isReduceTransparencyEnabled
变量,并监听reduceTransparencyStatusDidChangeNotification
,如下面的清单 8-18 所示。
import UIKit
class MyViewController: UIViewController {
var reduceTransparencyStatus: Bool {
get{
return UIAccessibility.isReduceTransparencyEnabled
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(reduceTransparencyChanged), name: UIAccessibility.reduceTransparencyStatusDidChangeNotification, object: nil)
}
@objc
func reduceTransparencyChanged() {
// check reduceTransparencyStatus for current status.
// add opacity to views as needed.
}
}
Listing 8-18Detecting changes in Reduce Transparency status
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
增加对比度
增加对比度是UIAccessibility
的较暗系统颜色选项的用户友好名称。然而,更暗的系统颜色是这个选项实际作用的一个更具描述性的名称——内置的苹果应用将使颜色变暗,特别是文本后面的颜色,以增加对比度(图 8-12 )。在“设置”中切换此选项,并查看顶部“后退”按钮的色调变化。另外,导航到消息并注意消息气泡颜色的差异。
图 8-12
增加信息的对比度(左)并关闭该功能(右)。请注意消息撰写栏中的深色文本和消息气泡上的深色背景
为了确定是否响应这个设置,使用 iOS 的 Accessibility API,访问isDarkerSystemColorsEnabled
变量,并监听darkerSystemColorsStatusDidChangeNotification
(列表 8-19 )。从 iOS 13 开始,这个设置也可以通过带有accessibilityContrast
属性的视图特征集合来确定(清单 8-20 )。该特征返回一个值为normal
或high
的枚举,反映客户的设置。
let contrast = traitCollection.accessibilityContrast
// returns an enum value of .high or .normal.
Listing 8-20Determining high-contrast status from a view’s trait collection
- 1
- 2
- 3
- 4
- 5
import UIKit
class MyViewController: UIViewController {
var darkerColorsStatus: Bool {
get{
return UIAccessibility.isDarkerSystemColorsEnabled
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(darkerColorsChanged), name: UIAccessibility.darkerSystemColorsStatusDidChangeNotification, object: nil)
}
@objc
func darkerColorsChanged() {
// check darkerColorsStatus for current status
// replace colors/assets for high contrast alternatives
}
}
Listing 8-19Detecting if increase contrast is enabled
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
只要你为你的应用提供了高对比度的变体,iOS 就会为你处理颜色的切换。存储在素材目录中的图像和颜色素材都可以提供高对比度变体(图 8-13 )。打开 xcassets 文件,选择要为其创建高对比度变体的颜色或图像资源。从右侧的属性检查器中,切换“高对比度”复选框。对于每个选定的素材价值,将出现第二个变量。iOS 将根据您客户的设置自动为您选择合适的素材。在确定清单 8-11 中的设置状态后,任何没有存储在素材目录中的颜色或图像都需要在您的代码中进行切换。
图 8-13
素材目录中的普通和高对比度颜色变体
没有颜色区分
一些损伤,如色盲,会阻碍我们感知颜色的能力。因此,WCAG 的一个重要特点是以多种冗余形式向您的客户提供信息。例如,一个包含感叹号的红色警告三角形,与标签“错误”组合在一起,以三种方式向您的客户提供信息-形状、颜色和文本。
有时你的设计可能需要单独使用颜色,比如给文本标签或背景着色来暗示不同的状态。如果你的应用以这种方式使用颜色,它的意义很容易被任何对颜色有不同体验的人所遗忘。 4
为了确定你是否应该提供额外的形式来提供这些信息,请听shouldDifferentiateWithoutColor
和differentiateWithoutColorDidChangeNotification
,参见清单 8-21 。
import UIKit
class MyViewController: UIViewController {
var differentiateWithoutColorStatus: Bool {
get{
return UIAccessibility.shouldDifferentiateWithoutColor
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(differentiateWithoutColorChanged), name: NSNotification.Name(rawValue: UIAccessibility.differentiateWithoutColorDidChangeNotification), object: nil)
}
@objc
func differentiateWithoutColorChanged() {
// check differentiateWithoutColorStatus for current status.
// Add shapes or text to add meaning.
}
}
Listing 8-21Detecting changes in differentiate without color status
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
反转颜色
反转颜色有助于提高对比度,因为大多数应用通常会在浅色背景上显示深色文本;翻转在深色背景上提供较亮的文本,使文本突出显示(图 8-14 )。它也被那些对光或颜色敏感的人使用,因为它具有减少屏幕上亮色的效果。
经典反转改变了一切,这意味着你的图像通常会看起来完全错误。通过智能反转,iOS 使用对屏幕上出现的内容的一些判断来决定是否反转颜色。一般来说,文本内容和背景是反转的,而图像和视频保持标准颜色。iOS 在这方面并不是 100%成功;一般来说,如果你把内容放在图像上,图像通常会被反转。有些屏幕根本没有变化,还有一些浅色。为了更好地了解 Smart Invert 的功能,我建议您启用该功能,并在设备上导航一段时间。注意特定图像、色调等的差异。,而许多保持不变。
图 8-14
智能反转–文本和背景反转;图像保持其原始颜色
如果您发现应用的某些部分在不反转的情况下会工作得更好,UIView 包含了一个用于accessibilityIgnoresInvertColors
(列表 8-22 )的属性。此属性可以设置为 true,以确保无论用户的设备设置如何,您的颜色都保持不变。属性将防止设置此属性的视图及其所有子视图发生反转。明智地使用这个属性;在每个视图上将此设置为真可能会让设计者高兴,但可能会激怒选择启用此设置来帮助他们使用您的应用的客户。使用该属性是为了可用性,而不是为了设计。
// heroImage is a reference to a UIImageView on our screen
heroImage.accessibilityIgnoresInvertColors = true
Listing 8-22Setting an image to ignore the inverted color setting
- 1
- 2
- 3
- 4
- 5
颜色过滤器
颜色过滤器允许您的客户调整某些或所有颜色的显示方式,使那些可能在对比度或颜色方面有困难的人更容易确定不同的颜色。这些过滤器也可以用来减少颜色的活力或完全关闭颜色。不需要对您的应用进行更改,也不需要设置监听来检测这些过滤器,但值得注意的是,您的一些客户可能会选择使用这些过滤器,它们会改变您的应用的外观。
如果启用了大多数滤镜,它们不会向您的应用报告,但可以使用isGrayscaleEnabled
和grayscaleStatusDidChangeNotification
检测灰度(列表 8-23 )。
import UIKit
class MyViewController: UIViewController {
var grayscaleStatus: Bool {
get{
return UIAccessibility.isGrayscaleEnabled
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(grayscaleChanged), name: UIAccessibility.grayscaleStatusDidChangeNotification, object: nil)
}
@objc
func grayscaleChanged() {
// check grayscaleStatus for current status.
// Add shapes or text to add meaning.
}
}
Listing 8-23Detecting changes in Grayscale status
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
减少白点
减少白点减少颜色的强度,给它们添加灰色元素;这可以减轻眼睛疲劳,减少眩光,但可能会对您的一些 UX 或图像的外观产生轻微的变化。差别是微妙的,所以如果你已经确保你的设计有足够的对比度,这不应该有负面影响。
减少运动
“减少运动”选项对于受运动影响的人来说是必不可少的,因为特定的动画可能会引发头晕和恶心。这对患有焦虑症、多动症和自闭症的人来说也是无价的。有了这些损伤,大量的运动,尤其是外围的运动,会让人分心和心烦意乱。要了解可以触发的动画类型,请在手机上启用此选项。当你使用手机时,留意苹果禁用了哪些动画,比如启动应用时的缩放动画。快速动画、多个平面中的动画和缩放动画都可以产生效果,因此如果您利用这些,请考虑在触发动画之前收听isReduceMotionEnabled
,并使用替代的演示形式。该设置更改时发布的通知为reduceMotionStatusDidChangeNotification
(列表 8-24 )。
import UIKit
class MyViewController: UIViewController {
var reduceMotionStatus: Bool {
get{
return UIAccessibility.isReduceMotionEnabled
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(reduceMotionChanged), name: UIAccessibility.reduceMotionStatusDidChangeNotification, object: nil)
}
@objc
func reduceMotionChanged() {
// check reduceMotionStatus for current status.
// stop or reduce the intensity of animation.
}
}
Listing 8-24Detecting if Reduce Motion is enabled
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
朗读选择
当长按单词或文本选择时,朗读选择会在弹出功能区中添加一个额外的选项。除了复制、查找等标准选项之外。,出现一个发言按钮(图 8-15 )。轻按它,Siri 将只读取高亮显示的单词或选择。您可以使用isSpeakSelectionEnabled
检查这是否被启用,并使用speakSelectionStatusDidChangeNotification
监听该设置的变化(列表 8-25 )。
图 8-15
朗读选择
import UIKit
class MyViewController: UIViewController {
var speakSelectionStatus: Bool {
get{
return UIAccessibility.isSpeakSelectionEnabled
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(speakSelectionChanged), name: UIAccessibility.speakSelectionStatusDidChangeNotification, object: nil)
}
@objc
func speakSelectionChanged() {
// check speakSelectionStatus for current status.
// pause any audio or video playing.
}
}
Listing 8-25Detecting changes in Speak Selection status
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
朗读屏幕
Speak Screen 是苹果对 iOS 内置屏幕阅读器使用的友好名称。Speak Screen 比 VoiceOver 笨得多,因为它没有任何导航或控制功能。启用时,可以用两个手指从屏幕顶部向下滑动来激活 Speak Screen(图 8-16 )。然后,Speak Screen 会识别屏幕的内容部分,并从左上至右下阅读它们,忽略任何导航栏或标签栏按钮或标题。
图 8-16
朗读屏幕
“朗读屏幕”遵循 VoiceOver 使用的相同的isAccessibilityElement
属性,因此将跳过相同的视图和控制。该行为不同于 VoiceOver,因为用户无法控制阅读的内容和时间,这意味着他们不能选择跳过或重复章节。值得在你的应用上启用 Speak Screen,并在每个屏幕上激活它,以确定你的内容在以这种方式阅读时是否有意义。
可以在设置中启用朗读屏幕,而无需对设备的外观或行为进行任何更改。您可以通过检查isSpeakScreenEnabled
来确定该设置是否已启用。请注意,该属性返回 true 并不意味着在您的任何屏幕上都激活了 Speak ScreenspeakScreenStatusDidChangeNotification
可用于确定您的用户在使用您的应用时是否更改了该设置(列表 8-26 )。
import UIKit
class MyViewController: UIViewController {
var speakScreenStatus: Bool {
get{
return UIAccessibility.isSpeakScreenEnabled
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(speakScreenChanged), name: UIAccessibility.speakScreenStatusDidChangeNotification, object: nil)
}
@objc
func speakScreenChanged() {
// check speakScreenStatus for current status.
// pause any audio or video playing.
}
}
Listing 8-26Detecting changes in Speak Screen status
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
朗读屏幕将继续阅读屏幕上的所有可用内容,直到它到达底部;然后,它会尝试滚动以找到更多要阅读的内容。如果不能滚动,则尝试在父视图上滚动,依此类推,直到没有进一步的父视图;在这一点上,阅读停止。任何自然滚动的 UIKit 元素都会为您实现这种滚动。如果你通过任何其他方式滚动——比如在电子书中翻页或者在一系列入职屏幕中从右向左滑动,你应该实现清单 8-27 中的accessibilityScroll(_ direction: UIAccessibilityScrollDirection)
函数。如果滚动成功,您必须返回true
并发布pageScrolled
可访问性通知。
import UIKit
class MyViewController: UIViewController {
override func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool {
if lastPage() {
return false
}
present(nextPage(), animated: true) {
UIAccessibility.post(notification: .pageScrolled,
argument: nil)
}
return true
}
func nextPage() -> UIViewController {
// return your next content view controller
return NextViewController()
}
func lastPage() -> Bool {
// return true if this is the last page of this content
if isLastPage {
return true
}
return false
}
}
Listing 8-27Accessibility Scrolling
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
音频描述
AVFoundation
包含内置支持音频描述音轨。当您的客户启用此设置时,为您的媒体使用AVPlayer
实例将免费为您提供此功能,前提是您确保您的视频中嵌入了音频描述。任何好的视频编辑软件都允许你为描述添加辅助音轨。
深色模式
黑暗模式是 iOS 13 的新功能。您的应用开箱即可支持黑暗模式,无需任何更改。但是如果你没有做出改变,你的应用可能会在启用黑暗模式时看起来有点奇怪。黑暗模式虽然看起来整洁并节省电池寿命,但对于对光敏感的人来说是一个基本的辅助功能,更高的对比度有助于改善一些视觉障碍,如色彩不足和视觉模糊。
您可以通过显示和亮度下的设置应用切换黑暗模式(图 8-17 )。现在切换黑暗模式,测试你的应用,看看你可能需要做出改变。您可能需要添加一些颜色和图像资源的深色变体。我们将在下面讨论这个问题。
图 8-17
亮模式(左)和暗模式(右)下的显示和亮度设置
可以将颜色和图像的暗(或亮)变型添加到存储在素材目录中的任何素材。选择您想要添加深色变体的颜色或图像,然后在“属性”检查器的“外观”部分中选取“任何,深色”(图 8-18 )“任何”素材是 iOS 13 之前的设备上 iOS 将使用的值,其中黑暗模式不是一个功能。通常,这与“灯光”选项相同,但是如果需要指定灯光选项以及传统资源,请选择“任何,灯光,黑暗”选项。
图 8-18
素材目录中的深色和任何颜色变体
如果你需要基于颜色模式做进一步的 UI 修改,你可以查询你的视图的特征集合(清单 8-28 )。userInterfaceStyle
将返回一个枚举值.light
、.dark
或。.unspecified
针对 13 以下 iOS 版本。
let darkMode = traitCollection.userInterfaceStyle
// Returns .light, .dark, or .unspecified as an enum value.
Listing 8-28Checking Dark Mode status on a view
- 1
- 2
- 3
- 4
- 5
摘要
-
视觉因素是目前为止 iOS 辅助功能中最大的选择,所以试图支持所有这些功能可能会令人生畏。但坚持良好实践——使用 iOS 提供的控制、布局约束和素材目录——将大有裨益。
-
当您第一次使用 VoiceOver 时,它可能会让人感到困惑,但您会很快学会基础知识,并使它成为您开发工作流程的一部分。
-
动态类型是任何现代 iOS 应用必不可少的一部分。如果你不支持它,你的应用在 iOS 上不会有宾至如归的感觉,你的客户也会注意到。
在下一章,我们将继续关注 iOS 的辅助功能。我们将继续讨论 iOS 为行动受限的人所做的考虑,并讨论作为一名开发人员,你可以做些什么来让这些客户在你的应用中有宾至如归的感觉。
Footnotes 1
http://www.rfc-editor.org/rfc/bcp/bcp47.txt
2
https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/typography/
3
https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app
4
还要记住,如果你以这种方式改变背景或文本颜色,每种可能的颜色组合都应该通过 WGAG 规定的至少 4.5:1 的对比度。