我正在尝试将自定义 View 放入 UITableViewCell
中,它当然位于 UITableView
中。我想让这个自定义 View 可访问,所以我需要将它设为 UIAccessibilityContainer
(因为它包含几个未作为自己的 UIView 实现的视觉元素)。
当我这样做时,只要表格滚动,元素的位置就会变得一团糟。使用 VoiceOver 对元素进行分页时,它会自动滚动表格以尝试将所选元素在屏幕上居中,但随后 VoiceOver 认为该元素所在位置的轮廓不再与它在视觉上的位置对齐。
请注意,在屏幕截图中,检查器显示“第 4 行,元素 2”,但突出显示的区域是第 7 行中的某个随机位置,因为它恰好是第 4 行在自动滚动表格之前的位置。
我的想法是,当表格 View 滚动时,我可能必须使用 UIAccessibilityPostNotification()
来发布布局更改,但是当我不使用 UIAccessibilityContainer
感觉我不应该这样做,系统应该为我处理这个 - 但 UIAccessibilityElement
需要它的事实是 accessibilityFrame
在屏幕坐标中设置似乎确实对事物产生了影响。 (额外的问题:API 到底为什么要那样设计?为什么不相对于元素的容器或类似的东西定义框架?Arg.)
这是自定义 View 的实现,以防万一这里有什么东西导致了问题。对于完整项目 (Xcode 4),click here。
@implementation CellView
@synthesize row=_row;
- (void)dealloc
{
[_accessibleElements release];
[super dealloc];
}
- (void)setRow:(NSInteger)newRow
{
_row = newRow;
[_accessibleElements release];
_accessibleElements = [[NSMutableArray arrayWithCapacity:0] retain];
for (NSInteger i=0; i<=_row; i++) {
UIAccessibilityElement *element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
element.accessibilityValue = [NSString stringWithFormat:@"Row %d, element %d", _row, i];
[_accessibleElements addObject:element];
[element release];
}
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[[UIColor lightGrayColor] setFill];
UIRectFill(self.bounds);
[[UIColor blackColor] setFill];
NSString *info = [NSString stringWithFormat:@"Row: %d", _row];
[info drawAtPoint:CGPointZero withFont:[UIFont systemFontOfSize:12]];
[[[UIColor whiteColor] colorWithAlphaComponent:0.5] setFill];
NSInteger x=0, y=0;
for (NSInteger i=0; i<=_row; i++) {
CGRect rect = CGRectMake(12+x, 22+y, 30, 30);
UIAccessibilityElement *element = [_accessibleElements objectAtIndex:i];
element.accessibilityFrame = [self.window convertRect:[self convertRect:rect toView:self.window] toWindow:nil];
UIRectFill(rect);
x += 44;
if (x >= 300) {
x = 0;
y += 37;
}
}
}
- (BOOL)isAccessibilityElement
{
return NO;
}
- (NSInteger)accessibilityElementCount
{
return [_accessibleElements count];
}
- (id)accessibilityElementAtIndex:(NSInteger)index
{
return [_accessibleElements objectAtIndex:index];
}
- (NSInteger)indexOfAccessibilityElement:(id)element
{
return [_accessibleElements indexOfObject:element];
}
@end
编辑:我应该注意到,我已经尝试过在 -indexOfAccessibilityElement:
和 -accessibilityElementAtIndex:
中更新元素的 accessibilityFrame
的变体: code> 的想法是 VoiceOver 会在需要时以某种方式请求该元素,这将是更新内容的好时机。然而,这似乎也不起作用。我有点希望 VoiceOver 会自动请求重绘,但这似乎也行不通。 (将位置设置代码放在 -drawRect:
中的想法来 self 记得在 WWDC 上看到的关于此的内容,但我不清楚这是“最佳实践”还是恰好方便.)
最佳答案
我已经通过向可访问性方法添加一些副作用并与表格滚动委托(delegate)协作解决了您描述的问题。在 drawRect
方法中,我计算矩形的 local 坐标,因此我不需要在那里转换坐标,只需根据单元格的左上角计算它们.
然后,我修改了访问器以使用这样的副作用更新框架(注意 y 重置):
- (id)accessibilityElementAtIndex:(NSInteger)index
{
UIAccessibilityElement *element = [accesible_items_ get:index];
CGRect rect = element.accessibilityFrame;
rect.origin.y = 0;
element.accessibilityFrame = [self.window
convertRect:rect fromView:self];
return element;
}
虽然这对于初始 View 工作正常,但当用户滚动时您仍然会得到位移帧,因此在您的 TableView Controller 中实现以下滚动委托(delegate):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// This loop has a side effects, see the cell accesor code.
for (id cell in self.tableView.visibleCells)
for (int f = 0; [cell accessibilityElementAtIndex:f]; f++);
UIAccessibilityPostNotification(
UIAccessibilityLayoutChangedNotification, nil);
NSLog(@"Layout changed after scrollViewDidScroll");
}
根据表格的内容,并非所有单元格都可以响应可访问性方法,因此您可以先使用 respondsToSelector
查询每个单元格,以避免发送意外消息。
我还会在创建 UIAccessibilityElement
对象的单元格 setter 末尾发布 UIAccessibilityLayoutChangedNotification
,否则您将收到日志消息,说明您的元素消失或找不到.
这些更改使滚动在使用转子一个接一个地迭代元素时起作用,但如果用户使用三指手势滚动,您可能仍然会得到奇怪的结果。这是因为默认情况下,tableViews 一次滚动一个屏幕页面,这可能不会恰好与您的单元格具有相同的元素边界,并且转子会选择一个半可见的单元格。根据滚动方向和其他 UI 元素,半个可见单元格可能会重叠控制转子混淆。您需要实现分页滚动来控制避免这种行为。
关于iphone - TableView 中的 UIAccessibilityContainer,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5872453/