Привет, Хабр! Недавно я говорил про адаптацию приложений для незрячих и неподвижных людей. И не договорил!
Сегодня расскажу, как изменить поведение контролов с помощью

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

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

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

Пример с добавлением топпингов в пиццу:

С помощью состояний можно объяснить пользователю, что топпинг добавлен. Удобно поправить прямо внутри ячейки.
Есть ещё несколько необычных свойств. В нашем случае к экрану с пиццей они не подходят, но я всё равно расскажу про них, потому что информации о них мало. Возможно, это сэкономит вам время.
Видео про разные способы ввода:
Достаточно поставить галочку в IB, чтобы добавить поведение. С трейтом .adjustable так просто не получится, о нём отдельно.
И последний, особенно важный трейт
Примеры применений для
Переключатель теста. Настройка теста состоит из пяти кнопок: три для выбора размера пиццы и две для типа теста. Их стоит сгруппировать и подписать, чтобы вместо пяти осталось две: «Размер, средний. Элемент регулировки» и «Тесто, традиционное. Элемент регулировки».

Нужно сделать в 4 шага:
Теперь после свайпа вверх вызовется
Счетчик количества всего на свете. В данном блоке мы видим четыре контрола: кнопка минус, количество, кнопка плюс и цена. Можно объединить их в одну

Горизонтальные

В этот раз мы разобрали трейты: их типы, состояния и поведение. Это стандартный набор для типовых задач. Для сложных контролов можно использовать .adjustable.
В следующий раз посмотрим на решение типовых проблем: порядок обхода, модальные окна, индикаторы загрузки.
Сегодня расскажу, как изменить поведение контролов с помощью
accessibilityTraits и сделать жизнь незрячих чуть удобней. Знать работу этих трейтов (traits) важно, чтобы не писать свои костыли. 
В первой части мы начали разбираться с адаптацией приложений для незрячих с помощью VoiceOver: подписали контролы, сгруппировали их, исправили навигацию. В этой статье пойдём дальше и рассмотрим «особенности», которые можно дать контролам, чтобы улучшить их работу для незрячих людей и в целом повысить удобство использования приложения.
Особенности элементов управления — Trait collection
VoiceOver имеет стандартный набор «особенностей»
UITraitCollection, которые вы можете применять к контролам. Важно знать о них заранее, чтобы не придумывать своих решений. Я поделил их на три типа: Сразу буду показывать на примере экрана с карточкой пиццы:

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

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

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

С помощью состояний можно объяснить пользователю, что топпинг добавлен. Удобно поправить прямо внутри ячейки.
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:Переключатель теста. Настройка теста состоит из пяти кнопок: три для выбора размера пиццы и две для типа теста. Их стоит сгруппировать и подписать, чтобы вместо пяти осталось две: «Размер, средний. Элемент регулировки» и «Тесто, традиционное. Элемент регулировки».

Нужно сделать в 4 шага:
- Сделать контейнер с кнопками доступным.
- Поставить трейт
.adjustable. - Реализовать методы увеличения и уменьшения.
- Возвращать новое значение для
.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 рублей. Элемент регулировки». После вертикального свайпа изменится количество, а затем произнесётся новое значение вместе с ценой.
Горизонтальные
UICollectionView. Оказалось, что .adjustable удобно применять и для горизонтальных UICollectionView. Например, выбрать акцию в меню или машину в такси. 
Заключение
В этот раз мы разобрали трейты: их типы, состояния и поведение. Это стандартный набор для типовых задач. Для сложных контролов можно использовать .adjustable.
В следующий раз посмотрим на решение типовых проблем: порядок обхода, модальные окна, индикаторы загрузки.
Чтобы не пропустить следующую статью, подписывайтесь на мой канал Dodo Pizza Mobile.
А ещё у нас сейчас открыта одна вакансия в мобильном направлении. Так что я просто оставлю это здесь: Senior iOS Developer (Нижний Новгород).
AllDmeat
а с заголовком под картинкой не конфликтует?
akaDuality Автор
Немного конфликтует. Выходов несколько.
Еще можно пропустить картинку и поставить фокус на заголовок при открытии экрана через
UIAccessibility.postNotification. Об этом будет в следующей статье :-)В любом случае, это не будет большой проблемой для незрячего. Чуть неудобно, но не более.
DollaR84
Это вообще не проблема. Проблема когда ничего не подписано, и как бы пользователь не искал хоть что-то, а в ответ тишина. Тем более экранные дикторы обычно перед текстом заголовка так и говорят: заголовок такой-то. А на картинке: графика такая-то подпись. Так что лучше картинки тоже подписывать и не пропускать, хоть от них действительно толку мало, но все же они помогают незрячему при навигации по приложению, так как дополняют образ интерфейса. Построив интерфейс программы в своем воображении, включая эти картинки, потом проще искать прочие компоненты управления.
DollaR84
А вот это точно не стоит делать. В скрин ридерах есть выбор навигации, например: по символам, по словам, по заголовкам, по абзацам, и так далее. Навигация по заголовкам очень удобная и незрячие ею очень часто пользуются. Это касается и любых сайтов. Очень удобно прыгнуть по заголовку и попасть сразу на начало статьи, например. И не листать все меню, баннеры и прочие надстройки. И очень опечаливают те сайты, где не используют заголовки настоящие, которые тегом html идут, а делают обычный текст и просто свойствами CSS выделяют его. Крайне неудобно пользоваться такими сайтами. Ну обычно их сразу и покидают если есть более удобные альтернативы.
akaDuality Автор
Согласен, спасибо.