VoiceOver简介
VoiceOver是苹果“读屏”技术的名称,属于辅助功能的一部分。VoiceOver可以读出屏幕上的信息,以帮助盲人进行人机交互。 这项技术在苹果的各个系统中都可以看到,OS X,iOS,watchOS,甚至tvOS。
无障碍编程思路
平常要是我们用苹果原生的控件的话,那基本上是天生自带VoiceOver的,也就是所有的东西都配合的天衣无缝,但是呢,要是某些小按钮啊,神马的,我们没有妥妥的设置好label之类的东西,更甚者用image之类的东西来当按钮的,项目跑起来VoiceOver就不好用了
无障碍开发实现细节
1.针对于原生控件
原生界面基本的UI元素可以设置一些属性来改变VoiceOver的效果:
-
isAccessibilityElement
该UI元素是否支持无障碍辅助功能 -
accessibilityLabel
告诉用户这个是什么 -
accessibilityTraits
告诉用户这是什么控件 -
accessibilityValue
告诉用户这个控件的值是什么,常用在文本编辑或者可以修改的东西的地方 -
accessibilityHint
告诉用户,这是干啥用的
实例代码如下:
self.editButtonItem.isAccessibilityElement = YES;
self.editButtonItem.accessibilityLabel = @"编辑";
self.editButtonItem.accessibilityTraits = UIAccessibilityTraitButton;
self.editButtonItem.accessibilityHint = @"编辑表格中的数据";
2.自定义UIView上的子视图
在自定义view中有时包含了一些非标准控件或者非UIView子类的可触摸UI原素,比如通过draw方法画出来的区域,则以上的两种情况都不能实现无障碍体验,这种情况下,则需要实现UIAccessibilityContainer Protocol来实现.UIAccessibilityContainer Protocol是非正式协议。
具体实现步骤:
- 定义一个NSMutableArray属性 用于保存所有的accessible Elements
- 创建多个
UIAccessibilityElement
(根据子视图数量或需求),指定相关的属性,然后添加到数组中 - 实现
- (NSInteger)accessibilityElementCount
代理方法,指定支持无障碍体验的子视图数量 - 实现
- (BOOL)isAccessibilityElement
,指定父视图是否支持无障碍体验,父View如果为AccessibilityElement,子element将不响应VoiceOver,所以这里要返回NO - 实现
- (id)accessibilityElementAtIndex:(NSInteger)index
,返回对应index下的UIAccessibilityElement - 实现
- (NSInteger)indexOfAccessibilityElement:(id)element
,反对对应UIAccessibilityElement的index
实例代码如下:
#import "LogoView.h"
@interface LogoView ()
@property (nonatomic, strong)NSMutableArray *accessibleElements;
@property (weak, nonatomic) IBOutlet UIImageView *img_1;
@property (weak, nonatomic) IBOutlet UIImageView *img_2;
@property (weak, nonatomic) IBOutlet UIImageView *img_3;
@property (weak, nonatomic) IBOutlet UILabel *title_1;
@property (weak, nonatomic) IBOutlet UILabel *title_2;
@property (weak, nonatomic) IBOutlet UILabel *title_3;
@end
@implementation LogoView
+(LogoView *)loadLogoView {
return [[NSBundle mainBundle] loadNibNamed:@"LogoView" owner:nil options:nil].lastObject;
}
- (NSArray *)getAccessElement {
if (_accessibleElements) {
return _accessibleElements;
}
_accessibleElements = [[NSMutableArray alloc] init];
UIAccessibilityElement *element_1 = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
element_1.isAccessibilityElement = YES;
element_1.accessibilityFrame = [self convertRect:self.img_1.frame toView:nil];
element_1.accessibilityLabel = NSLocalizedString(@"游戏中心", nil);
element_1.accessibilityTraits = UIAccessibilityTraitImage;
[_accessibleElements addObject:element_1];
UIAccessibilityElement *element_2 = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
element_2.isAccessibilityElement = YES;
element_2.accessibilityFrame = [self convertRect:self.img_2.frame toView:nil];
element_2.accessibilityLabel = NSLocalizedString(@"全区排行", nil);
element_2.accessibilityTraits = UIAccessibilityTraitImage;
[_accessibleElements addObject:element_2];
UIAccessibilityElement *element_3 = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
element_3.isAccessibilityElement = YES;
element_3.accessibilityFrame = [self convertRect:self.img_3.frame toView:nil];
element_3.accessibilityLabel = NSLocalizedString(@"原创排行", nil);
element_3.accessibilityTraits = UIAccessibilityTraitImage;
[_accessibleElements addObject:element_3];
UIAccessibilityElement *element_4 = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
element_4.isAccessibilityElement = YES;
element_4.accessibilityFrame = [self convertRect:self.title_1.frame toView:nil];
element_4.accessibilityLabel = NSLocalizedString(@"游戏中心", nil);
element_4.accessibilityTraits = UIAccessibilityTraitStaticText;
[_accessibleElements addObject:element_4];
UIAccessibilityElement *element_5 = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
element_5.isAccessibilityElement = YES;
element_5.accessibilityFrame = [self convertRect:self.title_2.frame toView:nil];
element_5.accessibilityLabel = NSLocalizedString(@"全区排行", nil);
element_5.accessibilityTraits = UIAccessibilityTraitStaticText;
[_accessibleElements addObject:element_5];
UIAccessibilityElement *element_6 = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
element_6.isAccessibilityElement = YES;
element_6.accessibilityFrame = [self convertRect:self.title_3.frame toView:nil];
element_6.accessibilityLabel = NSLocalizedString(@"原创排行", nil);
element_6.accessibilityTraits = UIAccessibilityTraitStaticText;
[_accessibleElements addObject:element_6];
return _accessibleElements;
}
- (NSInteger)accessibilityElementCount {
return [self getAccessElement].count;
}
- (BOOL)isAccessibilityElement {
return NO;
}
- (id)accessibilityElementAtIndex:(NSInteger)index {
return [[self getAccessElement] objectAtIndex:index];
}
- (NSInteger)indexOfAccessibilityElement:(id)element {
return [[self getAccessElement] indexOfObject:element];
}
@end
3.表格中复杂的cell
表格cell中,如果子视图比较多且复杂的时候或者除文本视图以外包含图片按钮等子控件的时候,无障碍信息可能会出现播报内容不全的情况,需要手动修改
具体实现步骤:
- 在自定义cell中,出现特殊的子视图,如图片、按钮等,需要将特殊的视图设置成无障碍元素,并且设置对应的提示类型等相关属性
- 重写
- (NSString *)accessibilityLabel
方法,然后将对应的控件转成字符串拼接到一起,语音会按照最先拼接的字符串读出来
实例代码如下:
- (NSString *)accessibilityLabel {
NSString *scenery = [self.sceneryImg accessibilityLabel];
NSString *nameLabel = [self.name accessibilityLabel];
NSString *sexLabel = [self.sex accessibilityLabel];
NSString *ageLabel = [self.age accessibilityLabel];
NSString *delBtn = [self.deleteBtn accessibilityLabel];
return [NSString stringWithFormat:@"%@, %@, %@, %@, %@", scenery, nameLabel, sexLabel, ageLabel, delBtn];
}
4.列表滑动
表格在VoiceOver开启状态下,三个手指上下滑动的时候,语音默认是中英文混合提示,有一定的偏差,可以进行手动设置来修改相关语音提示
具体实现步骤:
- 重写
- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction
方法,参数列表中的参数是个枚举,可以判断出向上或者向下,然后算出表格的总行数,滑动的行数,修改改成指定的语音,语音提示会覆盖掉默认的语音提示
实例代码如下:
- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction
{
BOOL isScrollingUp = NO;
switch (direction) {
case UIAccessibilityScrollDirectionUp: {
isScrollingUp = YES;
} break;
case UIAccessibilityScrollDirectionDown: {
isScrollingUp = NO;
} break;
default:
// These cases are not handled
return NO;
}
NSInteger numberOfCellsToScroll = 1; // Any number you'd like
NSInteger newRow = -1;
if (isScrollingUp) {
newRow = [self.tableView indexPathsForVisibleRows][0].row - numberOfCellsToScroll;
} else {
newRow = [[self.tableView indexPathsForVisibleRows] lastObject].row + numberOfCellsToScroll;
}
NSLog(@"######newRow %ld", newRow);
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:newRow inSection:0]
atScrollPosition:UITableViewScrollPositionMiddle
animated:YES];
UIAccessibilityPostNotification(UIAccessibilityPageScrolledNotification,
[NSString stringWithFormat:@"当前滑到了第%ld行", newRow]);
return YES; // We handled the scroll
}
总结
简单点来说在App开发过程中关于VoiceOver我们需要关注如下几点:
- 界面上的AccessibilityElement有哪些
- AccessibilityElement的位置和形状
- AccessibilityElement的信息是什么(就是Element被选中后,被读出来内容)
- AccessibilityElement所能响应的的事件有哪些