VoiceOver на iOS: каждый контрол ведёт себя по-разному

в 7:38, , рубрики: accessibility, Dodo IS, Dodo Pizza Engineering, iOS, ios development, mobile, mobile development, UI, uicollectionview, UX, ux design, voice, voiceover, Блог компании Dodo Pizza Engineering, разработка мобильных приложений, разработка под iOS

Привет! Недавно я говорил про адаптацию приложений для незрячих и неподвижных людей. И не договорил!

Сегодня расскажу, как изменить поведение контролов с помощью accessibilityTraits и сделать жизнь незрячих чуть удобней. Знать работу этих трейтов (traits) важно, чтобы не писать свои костыли.

VoiceOver на iOS: каждый контрол ведёт себя по-разному - 1

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

Особенности элементов управления — Trait collection

VoiceOver имеет стандартный набор «особенностей» UITraitCollection, которые вы можете применять к контролам. Важно знать о них заранее, чтобы не придумывать своих решений. Я поделил их на три типа:

  1. Тип контрола.
  2. Состояние контрола.
  3. Особые свойства контролов.

Сразу буду показывать на примере экрана с карточкой пиццы:

VoiceOver на iOS: каждый контрол ведёт себя по-разному - 2

Тип контрола

VoiceOver знает про несколько базовых типов элементов. Часть из них уже настроена в вашем проекте, но всё равно расскажу про них.

Типы контролов используются для навигации: по ним можно быстро перемещаться с помощью ротора.

  • .staticText— для надписей, которые не меняются. Текст просто прочитается.
  • .header — заголовок: Добавить в пиццу, заголовок.
  • .button — кнопка. Основной способ подписывать активные контролы: Изменить состав, кнопка.
  • .image — картинка.
  • .link — ссылка. Редкий гость в приложениях, частый на сайтах.
  • .searchField — поиск.

Смотрим на примере:
VoiceOver на iOS: каждый контрол ведёт себя по-разному - 3

  1. Указываем заголовок. .staticText ставится автоматически для всех надписей, а вот .header для заголовка нужно поставить вручную. При этом нужен и .header и .staticText.
  2. Отмечаем место под картинку. В прошлый раз все маленькие картинки мы скрыли от VoiceOver, информативность не потеряли. В этот раз картинка большая, ее так просто не скрыть: место станет пустым, это странно. Помечаем картинку как .image и подписываем .accessibilityLabel = "Пицца Пепперони Фреш с перцем".

Конечно, кнопки закрытия и корзины надо подписать, об этом было в прошлой статье.

Состояние контрола

У контрола может быть три состояния: обычный, выбранный и отключенный. Интересно, что произносятся они в разное время и могут быть выбраны одновременно:

VoiceOver на iOS: каждый контрол ведёт себя по-разному - 4

  • .selected — добавляет «выбрано» перед названием контрола. Подходит для всех свитчеров и чекбоксов.
  • .notEnabled — добавляет «недоступно». Эта настройка не видна в Interface Builder и управляется только программно.

Пример с добавлением топпингов в пиццу:
VoiceOver на iOS: каждый контрол ведёт себя по-разному - 5

С помощью состояний можно объяснить пользователю, что топпинг добавлен. Удобно поправить прямо внутри ячейки. accessibilityTraits это OptionSet, поэтому к нему можно применять методы вставки .formUnion и удаления .formIntersection:

class ToppingCell: UICollectionViewCell {
    override var isSelected: Bool {
        didSet {
            if isSelected {
                accessibilityTraits.formUnion(.selected)
            } else {
                accessibilityTraits.formIntersection(.selected)
            }
        }
    }
    ...
}

Особые свойства контролов

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

  • .summaryElement — первое, что скажет приложение после запуска. Например, приложение погоды после запуска может сразу рассказать о температуре, а музыкальный плеер расскажет о включенной песне и исполнителе. В нашем случае можно говорить статус доставки, если заказа уже оформлен.
  • .updatesFrequently — штука для таймеров. Новое значение будет проговариваться раз в несколько секунд.
  • .causesPageTurn — скролит после прочтения. Вызовется accessibilityScroll(.next) у того контрола, который сможет это обработать. Смотрит по .firstResponder.
  • .startsMediaSession — обычно VoiceOver повторяет название нажатой кнопки, чтобы подтвердить действие. Это мешает, если контрол проигрывает звук. Включите этот трейт, чтобы VoiceOver не повторял название контрола.
  • .playsSound — стоит включить, если вы проигрываете собственный звук при фокусировании на контроле (если я всё правильно понял, сам ни разу не использовал).
  • .allowsDirectInteraction — для рисования и обработки жестов. Контрол сразу обрабатывает касание, будто VoiceOver выключен.
  • .keyboardKey — контрол начинает реагировать, как кнопка на клавиатуре. У VoiceOver есть несколько режимов ввода текста для таких случаев:

    standart typing — как простая кнопка в VoiceOver: сначала наведите фокус на букву, а затем нажмите дважды в любом месте, чтобы написать её. Набирать можно быстрее двумя руками: одним пальцем водить по клавиатуре (буквы будут озвучиваться) и касаться другим пальцем, чтобы подтвердить выбор клавиши.
    touch typing — однорукий ускоренный набор: водите пальцем по клавиатуре, чтобы озвучить кнопки. Отпустите палец, чтобы написать букву.
    direct touch typing — как обычный набор, будто VoiceOver выключен.

Видео про разные способы ввода:

Достаточно поставить галочку в IB, чтобы добавить поведение. С трейтом .adjustable так просто не получится, о нём отдельно.

Настраиваем могучий трейт .adjustable

И последний, особенно важный трейт .adjustable — элемент, который можно регулировать: так работают UIStepper и UISlider. Свайпните такой контрол вверх или вниз, чтобы изменить значение (не забывайте, что свайп влево/вправо переключит фокус на соседний элемент). Если у контрола есть UIPanGestureRecognizer, то можно тапнуть дважды и задержать второй тап, так жест сработает и можно управлять им напрямую, будто VoiceOver выключен.

Примеры применений для .adjustable:

Переключатель теста. Настройка теста состоит из пяти кнопок: три для выбора размера пиццы и две для типа теста. Их стоит сгруппировать и подписать, чтобы вместо пяти осталось две: «Размер, средний. Элемент регулировки» и «Тесто, традиционное. Элемент регулировки».

VoiceOver на iOS: каждый контрол ведёт себя по-разному - 6

Нужно сделать в 4 шага:

  1. Сделать контейнер с кнопками доступным.
  2. Поставить трейт .adjustable.
  3. Реализовать методы увеличения и уменьшения.
  4. Возвращать новое значение для .accessibilityValue.

override public func awakeFromNib() {
        super.awakeFromNib()
        
        isAccessibilityElement = true // 1
        accessibilityTraits = .adjustable // 2
    }
extension SegmentedControl {
    override public func accessibilityIncrement() { // 3
        controller.selectNext(increment: +1)
    }
    
    override public func accessibilityDecrement() { // 3
        controller.selectNext(increment: -1)
    }
    
    public override var accessibilityValue: String? { // 4
        get {
            return selectedSegment?.accessibilityValue
        } set { }
    }
}

Теперь после свайпа вверх вызовется accessibilityIncrement(), вы увеличите внутренний счётчик, и VoiceOver прочитает новое значение из accessibilityValue.

Счетчик количества всего на свете. В данном блоке мы видим четыре контрола: кнопка минус, количество, кнопка плюс и цена. Можно объединить их в одну view и превратить в один контрол: «Количество, 1, 575 рублей. Элемент регулировки». После вертикального свайпа изменится количество, а затем произнесётся новое значение вместе с ценой.

VoiceOver на iOS: каждый контрол ведёт себя по-разному - 7

Горизонтальные UICollectionView. Оказалось, что .adjustable удобно применять и для горизонтальных UICollectionView. Например, выбрать акцию в меню или машину в такси.

VoiceOver на iOS: каждый контрол ведёт себя по-разному - 8

Заключение

В этот раз мы разобрали трейты: их типы, состояния и поведение. Это стандартный набор для типовых задач. Для сложных контролов можно использовать .adjustable.

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

Чтобы не пропустить следующую статью, подписывайтесь на мой канал Dodo Pizza Mobile.

А ещё у нас сейчас открыта одна вакансия в мобильном направлении. Так что я просто оставлю это здесь: Senior iOS Developer (Нижний Новгород).

Автор: Рубанов Михаил

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js