构建您iOS应用程序的无障碍特性

要做到可用性,一个iPhone应用必须将用户界面元素中的相关信息提供给VoiceOver用户。更高层次上,这意味着您应该确保:

除了上述原则外,开发者还可以做一些事情来提升Voiceover用户对表格视图的体验,并确保应用中的动态元素始终无障碍。

让用户界面元素具有可访问性

正如无障碍与VoiceOver所提及的,用户界面元素如果能告知其为无障碍元素,那么该元素是可访问性的。尽管可访问并不能足够让用户界面元素对Voiceover用户可用,但这是应用程序进行无障碍化过程的第一步。

也如iOS无障碍API及工具中所述,标准的UIKit控件和视图是自动具有可访问性的。如果只使用了标准的UIKit控件,开发者可能不需要太多其他工作来确保应用的无障碍。在这种情况下,下一步工作是确保应用中这些控件提供的默认属性信息是有意义的。详情参见提供准确和帮助性的属性信息

如果创建一个自定义视图来展示信息或是与用户进行交互,开发者必须确保这些视图的无障碍特性。在完成这些之后,开发者还需要确保这些视图提供的无障碍信息可以帮助到用户。(参见提供准确和有用的属性信息)。

从无障碍的角度来说,自定义视图可能是一个独立的视图也可能是一个容器视图。独立视图(individual view)不包含其他需要无障碍化的视图。例如,一个自定义UIControl子类会显示一个图标,并且具有按钮的行为,但是除了按钮本身,其并不包含任何可以与用户进行交互的元素。请阅读让自定义的独立视图具有无障碍特性了解如何让一个独立视图具有无障碍特性。

另一方面,容器视图包含其他可以和用户进行交互的元素。例如,在一个自定义UIView子类中绘制一个几何图形,用户可以与该图形呈现的元素进行交互,这与容器视图不同。容器视图内的独立元素不能自动获得无障碍特性(因为它们不是UIView的子类),也不会提供任何无障碍信息。请阅读让自定义容器视图的内容具有无障碍特性了解如何让容器视图的内容具有无障碍特性。

让自定义的独立视图具有无障碍特性

如果应用包含一个需要与用户进行交互的自定义独立视图,开发者必须确保这个视图的无障碍特性。(回想一下,一个独立视图是指:一个视图不包含其它可与用户交互的视图。

除了使用Interface Builder构建自定义视图的无障碍特性,还可以通过两种编程方法让自定义独立视图具有无障碍特性。第一种方法是:在实例化自定义视图的代码中设置其无障碍状态。如下面的代码片段所示:

@implementation MyCustomViewController
- (id)init
{
  _view = [[[MyCustomView alloc] initWithFrame:CGRectZero] autorelease];
  [_view setIsAccessibilityElement:YES];
 
/* 此处设置属性*/
}

另一种方法是在您的自定义子类实现中,UIAccessibility协议中使用isAccessibilityElement。如下面的代码片段所示:

@implementation MyCustomView
  /*此处使用属性方法 */
 
- (BOOL)isAccessibilityElement
{
   return YES;
}

让自定义容器视图的内容具有无障碍特性

如果应用中展示了一个自定义视图,包含其他与用户交互的元素,则需要让包含元素分别具有无障碍特性。同时,开发者必须让容器视图本身不再具有无障碍特性,因为用户是与容器中的内容进行交互,不是和容器本身交互。

为此,您自定义的容器视图应该实现UIAccessibilityContainer协议。这个协议会定义一些方法让被包含元素存放于一个数组中。

如下代码片段展示了一个自定义容器视图的部分实现。注意,这个容器视图只会在调用UIAccessibilityContainer协议时,才会创建无障碍元素数组。因此,如果iPhone的无障碍状态并没有被激活,那就不会创建该数组。

表 2-1  实现自定义容器中的内容做为独立的无障碍元素

@implementation MultiFacetedView
- (NSArray *)accessibleElements
{
   if ( _accessibleElements != nil )
   {
      return _accessibleElements;
   }
   _accessibleElements = [[NSMutableArray alloc] init];
 
   /*  创建一个无障碍元素展示第一个被包含的元素,并初始化它做为MultiFacetedView的组件*/
   UIAccessibilityElement *element1 = [[[UIAccessibilityElement alloc] initWithAccessibilityContainer:self] autorelease];
 
   /* 此处设置第一个包含元素的属性*/
   [_accessibleElements addObject:element1];
 
   /* 为第二个包含元素执行相同步骤*/
   UIAccessibilityElement *element2 = [[[UIAccessibilityElement alloc] initWithAccessibilityContainer:self] autorelease];
 
   /*此处设置第二个包含元素的属性*/
   [_accessibleElements addObject:element2];
 
   return _accessibleElements;
}
 
/* 容器本身并不具有无障碍特性,所以MultFacetedView在isAccessiblityElement中应该返回NO*/
- (BOOL)isAccessibilityElement
{
   return NO;
}
 
/* 如下方法实施UIAccessibilityContainer协议 */
- (NSInteger)accessibilityElementCount
{
   return [[self accessibleElements] count];
}
 
- (id)accessibilityElementAtIndex:(NSInteger)index
{
   return [[self accessibleElements] objectAtIndex:index];
}
 
- (NSInteger)indexOfAccessibilityElement:(id)element
{
   return [[self accessibleElements] indexOfObject:element];
}
@end

提供准确和有用的属性信息

提供准确和有用的属性信息有以下两个步骤:

如果使用自定义的视图,开发者必须为它们提供适当的属性信息。请参阅制作有用的标签和提示创建提示指南定义恰当的特质

即使只是用标准的UIKit控件和视图,也可能找到比默认提供的属性信息更适合应用环境的内容。更多信息请阅读“优化默认属性信息”。

无论是标准还是自定义的UI元素,如果需要提供或者更改其无障碍属性,开发者都可以使用Interface Builder(请参阅在Interface Builder中自定义属性信息)或者编程化处理(请参阅 编程化处理自定义属性信息)。

优化默认属性信息

作为标准UIKit控件和视图内建无障碍特性的一部分,iOS也为这些元素提供了默认属性信息,这些信息可以向VoiceOver用户描述这些元素。大多数情况下,应用如果使用标准控件和视图,这种信息是合适的。但是,自定义属性信息可以增强VoiceOver用户的体验。

  • 如果用一个标准UIKit控件和视图显示系统提供的图标和标题,开发者首先需要确认是以预期的目的使用它(请参阅“iOS人机界面指南”了解更多信息)。然后认真考虑,在应用中,默认的标签属性是否准确地传达结果,如果没有,考虑提供一个提示属性。

    举例来说,如果在UIBarButtonItem对象中,开发者使用系统提供的加(+)图标,在导航栏中放置一个添加按钮,它会自动包含一个默认的标签属性:添加。如果用户可以明确地知道每次点击这个按钮会添加哪个项目,那就不需要提供提示属性。但是如果可能产生误解,开发者应该在应用中提供一个自定义的提示,用以描述使用该按钮的效果,比如“添加一个账户”或者“添加一个评论”。

  • 如果您在标签的UIKit视图中显示一个自定义的图标或者图像,比如UIBarButtonItem对象,需要提供一个自定义的标签属性用以描述它。

制作有用的标签和提示

当VoiceOver用户使用应用时,他们依赖VoiceOver的描述了解应用的作用以及如何使用,由于这些描述代表了大量的VoiceOver用户对于应用的体验,所以有必要尽可能让其准确有用。该指南会帮助开发者创建有用的标签和提示,让应用对于残障人士来说更加简单易用。

创建标签指南

标签属性定义了用户界面中的元素,无论是标准的还是自定义的,每个无障碍用户界面元素都必须为标签属性提供内容。

判定标签应该传达什么内容的一个好办法,就是思考视觉用户通过视觉能够看到什么。如果用户界面设计地很好,视觉用户应该可以通过阅读标题或理解图标,了解在当前应用中控件和视图的作用。这些就是VoiceOver用户可从标签属性获取到的信息。

如果提供一个自定义的控件和视图,或者开发者在标准控件或视图中展示一个自定义的图标,需要提供如下的标签(label):

  • 非常简要地描述元素:理想化的标签(label)是由一个单词构成,比如,添加、播放、删除、搜索、收藏、音量。

    努力设计应用,尽量用一个单词标识一个元素,并能使它在当前环境下的作用显而易见。然而,有时候可能需要使用一个简单的短语才能标识一个元素。遇到这种情况,创建一个短的句子,比如“播放音乐”、“添加姓名”或者“添加事件”。

  • 不要包含控件或者视图的类型:这类信息被包含在元素的特质属性中,应永远不重复出现在标签中(label)。

    例如,如果在一个添加按钮的标签中,包含控件类型,VoiceOver用户每次访问该控件的时候都会听到“添加按钮按钮”,这种体验非常讨厌,可能刺激用户停止使用您的应用。

  • 单词首字母大写:可以帮助VoiceOver用适当的音调读取标签。

  • 不要在结束处用句号:标签不是一句话,因此不应该使用句号结束。

  • 本地化:本地化无障碍属性中的所有字符串,一定能让应用被更广泛的受众使用。通常情况,VoiceOver是以用户设置的语言进行朗读。

创建提示指南

提示属性描述的是:在控件或视图上执行一个动作的结果。当元素标签无法明确指出动作结果的时候,应该给出一个提示。

例如:在应用中提供一个播放按钮,按钮周边环境可让用户很容易明白,他们按下按钮时会发生什么。但是,如果允许用户通过轻敲歌曲列表中歌名播放歌曲,开发者应该提供一个提示用以描述结果,因为列表项的标签仅介绍自己的属性(此例中指歌名),用户点击时不知道会发生什么。

如果用户在控件或者视图上的动作结果,不能通过标签明确的表明,创建一个这样的提示:

  • 尽量简单介绍结果:尽管没有多少控件和视图需要提示,也应尽可能的提供简短的介绍。避免了用户还没使用元素,就浪费大量时间去听取相关信息。

    但是,不要为了提示的简短,而丧失语法的清晰和优雅。例如,将“添加一个城市”,改为“添加城市”,这并不意味着提示的长度简短了,反而听起来更别扭,并且表意不够清晰。

  • 动词开头,省略主语:确认使用第三人称单数的动词形式。例如“Plays,” 而不是强制的,如“Play.”。要避免使用命令语式,因为其放在提示中,像是一条命令。比如,不要告诉用户“Play the song”,要告诉用户按下元素会“Plays the song.”。

    要想找到正确的用词,想象一下,您正在为一位朋友介绍一个控件的用法,可能会这样说:“点击这个控件播放歌曲”。通常该句话的第二个短语(此例中为“播放歌曲”)可以作为一个提示。

  • 大写字母开头,句号结尾:即使提示是一个短语,不是一个句子,也要在提示末尾使用句点,这样有助于VoiceOver以适当的语气进行播报。

  • 不要包含行为的名称或手势:一个提示不用告诉用户如何执行该动作,它告诉用户的是当行为发生时会发生什么。因此,不要创建类似于“存储保存您的修改”或者“后退返回到之前的屏幕”的提示。

    这极其重要,因为VoiceOver用户使用特定手势与应用进行交互。如果在一个提示中命名了不同的手势,会造成一定的混淆。

  • 不要包含控件和视图的名称:用户从标签属性里得到信息,所以开发者不应该在提示中重复相关信息。因此,不要创建类似于这样的提示“存储保存您的修改”或者“后退返回到之前的屏幕”。

  • 不要包含控件和视图的类型:用户已经知道控件表现为一个按钮或者搜索域,因为该信息可从元素特质获取。因此,不要创建类似于“按钮用于添加名称”或者“滑动用于控制比例”的提示。

  • 本地化:与无障碍标签类似,提示应该可以读取用户的偏好语言。

定义恰当的特质

特质属性包含一个或者多个独立特质,共同描述一个无障碍用户界面元素的行为。由于一些独立的特性可以组合起来一起描述一个单独元素,因此一个元素的行为可以被准确地表述。

一个标准的UIKit控件,如一个按钮或者文本域,在特质属性中会提供默认的内容。如果在应用中仅用标准UIKit控件(特指没有用任何方式自定义其行为),这些控件的特质属性不会有任何变化。

如果是自定义行为的标准控件,开发者可能需要将某个新的特质与该控件默认的特质组合。如果创建自定义的控件视图,开发者需要提供该元素特质属性的内容。

UI无障碍编程接口定义了12个独立的特质,其中某些是可以被组合的。某些特质是通过特定控件类型的行为标识一个元素(如按钮)或者对象类型(如图像)。其它特质通过描述特定元素的展示行为标识元素,如播放声音的功能。

如下的特质可用于在应用中标识元素:

  • 按钮(Button)

  • 链接(Link)

  • 搜索域(Search Field)

  • 键盘按键(Keyboard Key)

  • 静态文本(Static Text)

  • 图像(Image)

  • 播放声音(Plays Sound)

  • 选中(Selected)

  • 总结元素(Summary Element)

  • 常态更新(Updates Frequently)

  • 不可用(Not Enabled)

  • 空(None)

通常,控件相对应特质能够结合描述行为的特质,例如,“按钮”特质可能和“播放音乐”特质用以表述一个自定义控件,其表象是一个按钮,点击即可播放音乐。

大多数情况下,应该考虑特质对应特定控件,尤其是,按钮、链接、搜索域、键盘键的特质,是互斥的。这时,不应该使用超过一个特质去表述应用中的某个元素,如果此元素有其它行为,可以将第一个特质和某一个行为特质结合。

例如,假设显示在应用中的一个图像,其响应的是,当用户点击时会在iPhone中的Safari打开一个链接,开发者应该通过结合图像和链接的特质表述该元素。另如,一个键盘键当点击时修改其它键盘键,开发者应该通过结合键盘键和选中的特质标识该元素。

了解如何标识控件的案例,可以用Accessibility Inspector看在标准控件中已被设定的默认特质。更多关于如何使用Accessibility Inspector,请参阅通过Accessibility Inspector在iOS Simulator无障碍排错

在Interface Builder中自定义属性信息

安装iOS SDK 3.0后,获得的Interface Builder版本包含帮助应用达到无障碍特性的功能。如果应用包含标准UIKit的控件和视图,可通过Interface Builder做所有的无障碍工作。

使用Interface Builder,可以设置元素的无障碍状态,并且为标签(label)和特质(traits)属性提供自定义内容。为此,在nib文件中选择用户界面元素,并且打开Identity inspector,在inspector中的Accessibility处,应该可以看到如图2-1所示内容:

图 2-1  显示在Interface Builder中的标准文本域的默认无障碍信息

图2-1所示,用于nib文件中的标准文本域默认是无障碍的,并且标签(label)、提示(hint)、特质(traits)的属性中包含默认的信息,(注意,文本域显示占位符文本,默认的标签就是占位符文本),在Identify inspector中可以提供新的信息改变任何默认值。如在图2-2所示,(图2-2显示的也是Accessibility Inspector如何显示文本域中的无障碍信息。参阅通过Accessibility Inspector在iOS Simulator无障碍排错)。

图 2-2  在Interface Builder中提供无障碍信息

编程化处理自定义属性信息

如果愿意,开发者可以通过编程为属性提供自定义信息。如果开发者完全不用Interface Builder或者更喜欢在代码中生成视图,这可能是开发者想用的方法。

让自定义的独立视图具有无障碍特性中介绍过,开发者可以在视图子类或在代码里实例化视图设置无障碍信息。两种方法都是有效的,但是为什么开发者可能需要在子类中实现属性方法而不是通过代码实例化实现,原因是:如果显示数据是动态的,或者频繁改变,如日期,开发者应该实现子类方法返回需要的刷新数据。在这些情况下,如果仅在实例化子类时设置属性,返回的数据可能是过期的。

此章节的代码片段是基于让自定义的独立视图具有无障碍特性,包含一些属性特有的方法。例如:如果开发者想在子类中实现无障碍方法,将会用到类似表2-2的代码:

表 2-2  在自定义子类实现中提供属性信息

@implementation MyCustomView
- (BOOL)isAccessibilityElement
{
   return YES;
}
 
- (NSString *)accessibilityLabel
{
   return NSLocalizedString(@"MyCustomView.label", nil);
}
 
/* 这个自定义控件的表像为一个按钮*/
- (UIAccessibilityTraits)accessibilityTraits
{
   return UIAccessibilityTraitButton;
}
 
- (NSString *)accessibilityHint
{
   return NSLocalizedString(@"MyCustomView.hint", nil);
}
@end

如果您要在代码中使用UIAccessibility协议的属性设定方法,实例化自定义视图,将会用到类似于表2-3中的代码。

表 2-3  在代码中提供属性信息,实例化自定义的子类对象

@implementation MyCustomViewController
- (id)init
{
  _view = [[MyCustomView alloc] initWithFrame:CGRectZero];
 
  [_view setIsAccessibilityElement:YES];
  [_view setAccessibilityTraits:UIAccessibilityTraitButton];
  [_view setAccessibilityLabel:NSLocalizedString(@"view.label", nil)];
  [_view setAccessibilityHint:NSLocalizedString(@"view.hint", nil)];
}

优化表格视图无障碍特性

如果应用中展示了一个表格,每个单元格中不只包含文本(或除了文本)条目,开发者可以做某些事情让其无障碍。同样地,如果表格视图中每行展示的信息不止一条,那可以把这些信息整合到一条易于理解的标签中,以优化VoiceOver用户的体验。

如果应用中的表格单元包含不同元素的混合,检查用户是与单元进行交互还是与单元里的独立元素进行交互。如果用户需要访问单元里的独立元素,开发者应该:

开发者可能已经意识到表格单元中可能会包含多个项目,如文本和控件,被UI无障碍编程接口所定义,匹配容器视图的标准。然而,开发者不必将单元格标识为容器视图,或实现UIAccessibilityContainer协议中的任何方法,因为表格单元被自动指派为一个容器。

如果表格中包含单元格,这些单元格在离散组块中提供信息,开发者应该考虑从这些标签属性的组块中组合信息。当实现该功能,VoiceOver用户可以仅使用一个手势就能获取单元格内容的意义,而不需要单独访问每个独立块中的信息。

用一个很好的例子解释该功能的效果:内建股票应用,它不用单独字符串去提供公司名、当前股票价格和价格波动,而是组合到一个标签(label)中,这样听起来类似:“苹果公司,432.39美元,涨幅1.3%。”。注意标签中的逗号,当结合多个片段的时候,可以使用逗号,这样VoiceOver就会在每个逗号短暂停留,让用户更容易理解信息。

此处的代码片段展示了如何组合两个独立元素中的信息到一个单独标签:

@implementation CurrentWeather
/* 该视图提供了天气信息,它包涵一个城市子视图和一个温度子视图,每个视图有独立标签 */
- (NSString *)accessibilityLabel
{
    NSString *weatherCityLabel = [self.weatherCity accessibilityLabel];
    NSString *weatherTempLabel = [self.weatherTemp accessibilityLabel];
 
    /* 结合城市和温度的信息,VoiceOver用户可以使用一个手势来获取天气信息*/
    return [NSString stringWithFormat:@"%@, %@", weatherCityLabel, weatherTempLabel];
}
@end

让动态元素具有无障碍特性

如果应用中的界面元素会动态改变,开发者需要确认元素提交的无障碍信息是准确及时的。当应用屏幕布局改变发生时,需要发送一个通知,这样VoiceOver能够帮助用户导航新布局。UI无障碍编程接口提供两个通知类型,可以用在这类屏幕改变事件发生的情况下。(了解这些通知,请在 UIAccessibility协议参考中的“通知”。)

如果用户界面元素根据不同应用条件下有不同的状态,那开发者需要在代码中加入返回元素所有状态对应无障碍信息的逻辑。由于这些改变是用户的动作的结果,所以最好把逻辑添加到子类实现中,而不是实例化子类的代码中。

下面的代码展示了如何处理动态状态的改变以及如何在屏幕布局发生改变时发送通知。代码中的UIView子类的行为类似一个自定义键盘按键。按键的无障碍标签会根据实例是否代表一个字母或其他类型的字符,和shift键是否按下来进行改变。按键也会根据实例内容和选中状态返回不同的无障碍特性。注意,表2-4的代码假设会有许多方法查询键盘的状态:

表 2-4  为当前情况返回正确的无障碍信息,并且发送布局改变通知

@implementation BigKey
- (NSString *)accessibilityLabel
{
   NSString *keyLabel = [_keyLabel accessibilityLabel];
   if ( [self isLetterKey] )
{
      if ( [self isShifted] )
      {
         return [keyLabel uppercaseString];
      }
      else
      {
         return [keyLabel lowercaseString];
      }
   }
   else
   {
      return keyLabel;
   }
}
 
- (UIAccessibilityTraits)accessibilityTraits
{
   UIAccessibilityTraits traits = [super accessibilityTraits] | UIAccessibilityTraitKeyboardKey;
 
   /*是否这是一个shift键,并被按下,用户需要知道shift当前是有效的*/
   if ( [self isShiftKey] && [self isSelected] )
   {
      traits |= UIAccessibilityTraitSelected;
   }
 
   return traits;
}
 
- (void)keyboardChangedToNumbers
{
   /* 此处代码,执行一个数字键盘的变化 */
 
   /*发送一个屏幕布局改变的通知。*/
 
   UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
@end

让非文本数据具有无障碍特性

有时应用会展示那些不是自动兼容无障碍方法的数据。例如,如果展示一个图像,开发者应该在它的无障碍标签中提供它的描述,这样VoiceOver用户就可以理解图像传达的信息。或者,如果使用图形方式提供信息,比如显示星级的评价系统,开发者应该确认无障碍标签传达了图形所表达的意思。

如下的代码段使用自定义视图样例展示了与评分条目对等的星级条目。该代码展示了视图如何根据绘制的星星数量返回一个恰当的无障碍标签。

@implementation RatingView
/* Other subclass implementation code here. 在此处实现其它子类的代码。*/
 
- (NSString *)accessibilityLabel
{
   /*_starCount是一个实例化变量,包含了画多少颗星星*/
   NSInteger starCount = _starCount;
   if ( starCount == 1 )
   {
      ratingString = NSLocalizedString(@"rating.singular.label", nil); //此处,ratingString是星数
   }
   else
   {
      ratingString = NSLocalizedString(@"rating.plural.label", nil); // 此处,ratingString是星数
   }
 
   return [NSString stringWithFormat:@"%d %@", starCount, ratingString];
}
@end