VoiceOver是苹果“读屏”技术的名称,属于辅助功能的一部分。VoiceOver能够读出屏幕上的信息,以帮助盲人进行人机交互。 这项技术在苹果的各个系统中均可以看到,OS X,iOS,watchOS,甚至tvOS。 苹果公司的VoiceOver在2015年6月18日得到了美国盲人基金会(American Foundation for the Blind, AFB)颁发的海伦凯勒成就奖,成为全球首家得到此殊荣的科技公司。 单从iOS来讲,iOS的VoiceOver功能能够绝不夸张的说是三大移动平台中作的最好的。数组
虽说苹果默认的UI组件都已经默认支持VoiceOver功能了,可是一般状况下App仍是须要对VoiceOver进行适配和优化的,好比说一些自定义复杂UI组件。app
iPhone上开启VoiceOver功能后,就能够经过单指左右轻扫来遍历当前界面中的全部的AccessibilityElement(能够被VoiceOver访问的UI元素),当一个AccessibilityElement被选中后,VoiceOver会将AccessibilityElement的信息读出来。单指轻点两次可以激活当前元素对应的操做,好比当前AccessibilityElement是一个按钮,那么对应的就是按钮的Action事件。函数
简单点来讲在App开发过程当中关于VoiceOver咱们须要关注以下几点:优化
界面上的AccessibilityElement有哪些设计
AccessibilityElement的位置和形状code
AccessibilityElement的信息是什么(就是Element被选中后,被读出来内容)orm
AccessibilityElement所能响应的的事件有哪些事件
UIKit中的控件基本都是 VoiceOver Ready的,即便是UIView,你也能够经过简单的设置其实变成AccessibilityElement。因此这一小节中所讲的AccessibilityElement其实都是UIView或其子类的实例。 element
相关属性和方法基本都在UIAccessibility.h
这个头文件中进行了声明。开发
因此简单的状况下经过UIView的isAccessibilityElement
属性就能够控制某个View是不是AccessibilityElement,在UIKit的控件中,像UILabel,UIButton 这些控件的isAccessibilityElement属性默认就是true的,UIView这个属性默认是false。
通常状况下AccessibilityElement的位置和形状是经过accessibilityFrame进行设置的,默认值是View在屏幕中的位置,形状就是View的矩形形状。若是你想本身设置accessibilityFrame的值,那么得注意下,这边的frame值是相对于设备Screen的坐标系的,固然能够经过UIAccessibilityConvertFrameToScreenCoordinates函数来帮助转换。此函数有两个参数,一个rect,一个是view, 其含义就是将相对于view这个坐标系的rect转换成相对于screen坐标系的值并返回。因此通常状况下 rect能够是目标Element在父View中的frame,view就为其父view。
public func UIAccessibilityConvertFrameToScreenCoordinates(rect: CGRect, _ view: UIView) -> CGRect
若是你想设置非矩形的形状,你也能够经过给accessibilityPath属性指定一个UIBezierPath类型的值来自定义AccessibilityElement的形状。
至于AccessibilityElement的信息能够经过下面几个UIAccessibility的属性来决定
accessibilityLabel 这是什么
accessibilityHint 这个有什么用,会产生什么样的结果
accessibilityValue 这个的值是什么
accessibilityTraits 这个的类型以及状态,就是经过traits来表征这个Element的特质,数据类型是一个枚举类型,能够经过按位或的方式合并多个特性。
这里有个须要注意的就是,当某个View的是AccessibilityElement的时候 ,其subviews都会被屏蔽掉,这个特性有时候仍是有用的,好比一个View中包含多个Label,那么你但愿每个下面的Label不要单独能够访问到,那么你能够将这个View设置成能够访问的,而后将其accessibilityLabel设置为全部子Label的accessibilityLabel的合并值。
至于AccessibilityElement的事件,最简单的莫过于上面提到单指轻点两次可以激活当前元素对应的操做了,若是当前AccessibilityElement实现的public func accessibilityActivate() -> Bool
这个方法返回true,那边此逻辑将被调用,不然至关于在AccessibilityElement的accessibilityActivationPoint这个位置点上进行了一次Tap操做。
#### Accessibility Container 设想下这样的一个场景,一个UIView,内部包含一组用户能够进行交互的内容,每个内容之间是独立的,可是这些内容不是以子View的形式存在,而是经过Quarz 2D或者Core Text渲染而成,因此这部份内容没法经过上面的方式变成AccessibilityElement。这种状况UIView须要按照UIAccessibilityContainer的方式,来将内部的每个独立的内容都描述成UIAccessibilityElement的实例,这个时候这个UIView咱们称之为Accessibility Container。
接下来说解具体的实现步骤了。 先说说iOS 8以后如何实现,首先Accessibility Container的isAccessibilityElement值必须设置为false,另外咱们须要建立出全部的UIAccessibilityElement的实例,而后赋给accessibilityElements属性(iOS 8.0+) 假设在一个UIView的子类中,经过叫作updateAccessibleElements的方法来更新维护全部的UIAccessibilityElement实例,当界面上的内容发生变化,或者VoiceOver开启关闭状态发生变化时,调用此方法以更新accessibilityElements,相关伪代码以下:
internal func updateAccessibleElements() { guard UIAccessibilityIsVoiceOverRunning() else { self.accessibilityElements = nil return } self.isAccessibilityElement = false var elements = [AnyObject]() let element1 = UIAccessibilityElement(accessibilityContainer: self) element1.accessibilityLabel = "element1" element1.accessibilityTraits = UIAccessibilityTraitStaticText element1.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(element1FrameInSelf, self) elements.append(element1) ... self.accessibilityElements = elements }
若是是iOS 8如下,那么就须要实现下面的几个方法来实现了,比较繁琐:
// accessibilityElement的个数 public func accessibilityElementCount() -> Int // 返回指定Index的accessibilityElement public func accessibilityElementAtIndex(index: Int) -> AnyObject? // 返回指定accessibilityElement的Index public func indexOfAccessibilityElement(element: AnyObject) -> Int
使用上面第二种方式的时候,每每须要本身维护一个包含全部accessibilityElements的数组,而后经过数组来完成上面的这三个方法的返回。这种方法比较累赘,并且当界面更新须要刷新内容的时候,还须要发送通知系统,告诉系统界面上的accessibilityElements有变更须要更新。因此最小版本定位于iOS 8的状况下仍是直接设置accessibilityElements属性的方式比较科学。
关于UIAccessibilityElement这个类的设计仍是存在一些疑惑的,既然NSObject的UIAccessibility扩展已经包含了诸如accessibilityLabel
,accessibilityHint
这些属性,为什么UIAccessibilityElement类中还须要重复声明这些属性,有点重复的感受。
以前只讲到了最简单的事件,就是单指轻点两下,其实常见的Actions有下面这些,每个Action都会对应一个方法,能够经过覆盖方法的方式来自定义Action对应的逻辑:
Activate 单指轻点两次 public func accessibilityActivate() -> Bool
Escape. 单指 Z-shaped 手势通常用于退出模态界面或者返回导航的上一页界面 public func accessibilityPerformEscape() -> Bool
Magic Tap. 双指轻点两次触发 most-intended action. public func accessibilityPerformMagicTap() -> Bool
Three-Finger Scroll. 三指滑动触发界面水平或者垂直的滚动 public func accessibilityScroll(direction: UIAccessibilityScrollDirection) -> Bool
Increment. 单指向上滑动,须要设置accessibilityTraits为UIAccessibilityTraitAdjustable,不然对应的方法不会被调用 public func accessibilityIncrement()
Decrement. 单指向下滑动,须要设置accessibilityTraits为UIAccessibilityTraitAdjustable,不然对应的方法不会被调用 public func accessibilityDecrement()
这些方法中,其中Escape,Magic Tap,Three-Finger Scroll这几种手势支持在响应链中向上寻找对应的Action方法,首先用户在屏幕上进行相应的手势,系统检查当前VoiceOver的Focus的Element有无实现对应的方法,没有实现的话,则向响应链的上一级寻找。好比有些全局性的操做,对应的方法写在上层View或者ViewController中比较合适。 其实不少系统提供的组件都默认实现了一些VoiceOver的手势Action,好比UINavigationController, UIAlertController 都提供了对Escape手势的支持。
Accessibility提供了一系列的通知,能够完成一些特定的需求。好比你能够监听UIAccessibilityVoiceOverStatusChanged通知,来监控Voice Over功能开启关闭的实时通知
。
或者是你在App中主动发送一些通知,来让系统作出一些变化,好比当你界面上的AccessibilityElement有变更的时候你能够发送UIAccessibilityLayoutChangedNotification
通知,通知发送时使用UIAccessibility的专用的函数,UIAccessibilityPostNotification函数有两个参数,第一个是通知名,第二个是你想让VoiceOver读出来的字符串或者是新的VoiceOver的焦点对应的元素。
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.myFirstElement)
AccessibilityElement的信息尽可能简洁,accessibilityLabel不要包含提示性质的文案,避免信息干扰
TableView的每个Cell的信息尽可能合并,使得Cell变成一个总体的AccessibilityElement,避免无心义的冗余元素之间切换的操做。Cell中有多个按钮的时候,能够考虑使用Magic Tap的方式,Magic Tap的Action中弹出sheet样式的UIAlertController来供用户操做。
自定义的模态页面注意设置accessibilityViewIsModal为true,最好支持Escape手势 的方式退出模态页面。
将页面中装饰用的没有实际意义的元素的accessibilityElementsHidden设置成true,减小操做过程当中的干扰。