UIAccessibilityContainer в табличном представлении

#iphone #objective-c #cocoa-touch #uikit #accessibility

#iPhone #objective-c #cocoa-touch #uikit #Специальные возможности

Вопрос:

Я пытаюсь поместить пользовательский вид внутри a, UITableViewCell который, конечно, находится внутри UITableView . Я хочу сделать это пользовательское представление доступным, поэтому мне нужно сделать его UIAccessibilityContainer (поскольку оно содержит несколько визуальных элементов, которые не реализованы как их собственные UIViews).

Когда я делаю это, расположение элементов становится беспорядочным всякий раз, когда прокручивается таблица. При пролистывании элементов с помощью VoiceOver будет автоматически прокручиваться таблица, чтобы попытаться центрировать выбранный элемент на экране, но затем контур того места, где, по мнению VoiceOver, элемент находится, визуально больше не совпадает с тем, где он находится.

сломан

Обратите внимание, что на скриншоте инспектор говорит «Строка 4, элемент 2», но выделенная область является случайным местом в строке 7, поскольку именно там находилась строка 4 до автоматической прокрутки таблицы.

Я подумал, что мне, возможно, придется использовать UIAccessibilityPostNotification() для публикации изменений макета при прокрутке табличного представления, но мне не нужно этого делать, когда я не использую UIAccessibilityContainer , и мне кажется, что мне не нужно этого делать, и что система должна справиться с этим за меня — но тот факт, что UIAccessibilityElement необходимо, чтобы это accessibilityFrame было задано в координатах экрана, похоже, вносит путаницу в ситуацию. (Дополнительный вопрос: какого черта API разработан таким образом? Почему бы не определить фрейм относительно контейнера элемента или что-то в этом роде? Аргумент.)

Вот реализация пользовательского представления на всякий случай, если здесь есть что-то, что вызывает проблему. Для полного проекта (Xcode 4) нажмите здесь.

 @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
  

Редактировать: Я должен отметить, что я пробовал варианты, которые обновляют элементы accessibilityFrame в -indexOfAccessibilityElement: и -accessibilityElementAtIndex: с идеей, что VoiceOver каким-то образом запросит элемент, когда ему это понадобится, и было бы неплохо обновить вещи. Однако, похоже, это тоже не работает. Я надеялся, что, возможно, VoiceOver автоматически запросит что-то перерисовать, но это тоже, похоже, не работает. (Идея поместить код настройки местоположения в -drawRect: возникла из того, что я, помнится, видел на WWDC по этому поводу, но мне было неясно, было ли это «наилучшей практикой» или просто оказалось удобным.)

Комментарии:

1. Вы проверили поведение на реальном устройстве, а не просто ошибку в симуляторе?

2. Да, это также некорректно работает на устройстве. Он ведет себя немного по-другому, но все равно не так, как я ожидал.

3. Я знаю, что это безумно старый. На всякий случай, если кто-нибудь еще наткнется на это сегодня: представлена iOS 10 accessibilityFrameInContainerSpace , которая должна решить эту проблему.

Ответ №1:

Я решил проблему, которую вы описали, добавив некоторые побочные эффекты к методам доступности и в сотрудничестве с делегатами прокрутки таблицы. Внутри drawRect метода я вычисляю локальные координаты прямоугольника, поэтому мне не нужно преобразовывать координаты там, просто вычисляю их относительно верхнего левого угла ячейки.

Затем я изменил средство доступа, чтобы обновить фрейм с помощью такого побочного эффекта (обратите внимание на сброс 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;
}
  

Хотя это отлично работает для начального представления, вы все равно получаете смещенные кадры при прокрутке пользователем, поэтому в вашем контроллере табличного представления реализуйте следующий делегат прокрутки:

 - (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 , чтобы избежать отправки неожиданных сообщений.

Я бы также разместил сообщение UIAccessibilityLayoutChangedNotification в конце настройки ячеек, создающего UIAccessibilityElement объекты, иначе вы получите сообщения журнала, в которых говорится, что ваши элементы исчезли или не могут быть найдены.

Эти изменения заставляют прокрутку работать при повторении элементов один за другим с помощью ротора, но вы все равно можете получить странные результаты, если пользователь прокручивает их тройным жестом пальца. Это потому, что по умолчанию tableViews одновременно прокручивает страницу экрана, границы элементов которой могут отличаться от границ ваших ячеек, и ротор выбирает ячейку, видимую наполовину. В зависимости от направления прокрутки и других элементов пользовательского интерфейса, половина видимой ячейки может перекрывать элементы управления, с которыми путается ротор. Вам необходимо реализовать прокрутку по страницам, чтобы избежать такого поведения.

Ответ №2:

Влияет ли эта проблема на удобство использования приложения в режиме VoiceOver? Когда я играл с VoiceOver как в Mac OS, так и в iOS, поля выделения (особенно в веб-представлениях) часто не соответствуют их экранным объектам. Если приложение все еще можно использовать в VoiceOver, я бы назвал это известной ошибкой и исправил ее, если кто-то пожалуется.

В конце концов, большинство слепых людей, которых я знаю, не смотрят на выделенные поля.

Комментарии:

1. Существуют другие типы вспомогательных технологий, которые видны пользователям. Например, Switch Control позволяет управлять iPhone с помощью внешнего устройства, такого как клавиатура, какой-либо датчик или даже с помощью мимики лица! Пользователям Switch Control нужны правильные рамки элементов