Представь, что ты незрячий. Вот так ты услышишь эту картинку: «Буква D в чёрных очках и с белой палочкой и буква O в инвалидной коляске смотрят на большой телефон. На экране телефона скриншот приложения Додо Пиццы, который проговаривает названия ячейки меню из пицц для VoiceOver. Картинка стилизована под старые компьютеры и выполнена в зелёных цветах. Нажать кнопку «Читать дальше».
Телефон давно стал продолжением меня, и я слабо представляю свою жизнь без пары десятков приложений, которыми пользуюсь каждый день. Но как быть тем, кто не может взять телефон в руку или посмотреть на экран? Фичи iOS открывают людям с ограничениями в движении, зрении и слухе эти возможности в обычной жизни. С их помощью можно увеличить размер текста и контраст, сделать моно-аудио, убрать анимации. Можно работать с интерфейсом без экрана — на слух (для незрячих) или вообще управлять только голосом (если человек ограничен в движении).
Адаптировать можно любое приложение и даже некоторые игры. Сегодня я расскажу как iOS-разработчикам сделать первые шаги в этом направлении.
Voice Control: управляй голосом
Начиная с iOS 13 телефон можно контролировать голосом. Voice Control упрощает жизнь и даёт новый уровень свободы людям с ограничениями в движении. Посмотрите видео, в нём Apple показывает, как именно это работает:
Ещё более детально об управлении телефоном можно узнать вот в этих видео:
- Навигация голосом в вашем iPhone (How to navigate with Voice Control on your iPhone).
- Как использовать диктовку и редактировать текст с помощью голосового управления на вашем iPhone (How to use dictation and edit text with Voice Control on your iPhone).
Включив эту функцию, вы сможете отдавать команды телефону. Увы, пока работает только с английским языком. Например, говоришь «tap purchase», — и кнопка купить нажимается. Для управления кнопками с иконками (без названий в виде слов) можно скомандовать «show numbers», и у всех кнопок появятся цифровые подписи. Теперь можно сказать «tap five» и пятая кнопка нажмётся.
Для работы со сложными элементами (карты, графики), можно попросить телефон показать сетку, тогда место на карте можно будет выбирать по номеру ячейки.
Доступных жестов очень много. Полное описание всех вы можете посмотреть в настройках телефона: Settings → Accessibility → Voice Control → Customize Commands.
VoiceOver: управляй жестами
Чтобы вашим приложением могли пользоваться незрячие люди, его нужно адаптировать с помощью VoiceOver. Несколько отличий от обычного использования:
- Вместо взгляда по экрану скользит палец. Когда палец оказывается на кнопке, телефон говорит её название и наводит на неё фокус в виде чёрной рамки. После этого можно нажать дважды в любом месте, кнопка нажмётся. Ещё можно переключаться между соседними элементами свайпом влево или вправо.
- Доступны дополнительные жесты: для навигации, для важных действий, для сложных элементов управления, например, слайдеров.
- Экран можно выключить, ведь он не нужен. Для этого тапните тремя пальцами трижды.
Полный список доступных жестов.
Как сделать ваше приложение доступным
Voice Control и VoiceOver работают на одной технологии, поэтому адаптировав одно, мы получаем поддержку второго.
Для начала нужно побыть пользователем: включить, попробовать поюзать самостоятельно и настроить шорткат (быстрое включение), чтобы легко было проверять новые фичи.
Где включить: включать/отключать можно через Siri или через настройки (Settings → Accessibility → VoiceOver).
Как настроить шорткат: для быстрого доступа включите шорткат на тройное нажатие кнопки «домой» (или «выключить» для X моделей): Settings → Accessibility → Accessibility Shortcut → Поставить галочку рядом с VoiceOver
.
Программируем (теория)
Основа доступности — протокол UIAccessibilityElement. Для улучшения работы VoiceOver
нужно:
- Подписать кнопки.
- Добавить значения.
- Оставить подсказки.
- Сгруппировать контролы.
- Поправить неправильные надписи.
- Указать тип контрола: кнопка, надпись, ссылка и т.д.
Что-то можно настроить в Interface builder
, но часть настроек доступна только в коде.
Названия кнопок — .accessibilityLabel
Каждой кнопке надо дать короткое звучное название. VoiceOver
подстрахует, если вы забыли — попытается прочитать текст или название иконки на кнопке, но часто получается так себе.
Что нужно подписывать:
- Кнопки с иконкой, но без текста;
UISlider
;UIStepper
;- Картинки. Если есть возможность, то лучше подписать, что изображено на картинке. Instagram это умеет.
Значения — .accessibilityValue
Дополнительно к названию можно написать значение. Например, у слайдера будет название «яркость», а значение — «50%». У кнопки «Добавить в корзину» стоит указать количество или итоговую цену, чтобы подытожить действие всего экрана и не купить лишнего.
Подсказки — .accessibilityHint
Если вы хотите дополнительно пояснить действие, то можно записать подсказку в .accessibilityHint
. Но сильно полагайтесь на подсказки не стоит: постоянные пояснения надоедают, поэтому некоторые пользователи отключают их через настройки телефона.
Обобщение контролов
По умолчанию проговаривается каждый контрол по отдельности. Это неудобно: зоны нажатий уменьшаются, можно что-то не заметить и т.д. Нужно обобщать. Например, в меню ячейка состоит из картинки, названия, описания и кнопки с ценой. Такая детальность не нужна: маленькую картинку можно скрыть, название и цену прописать в заголовок ячейки, а состав в её значение. Ячейка станет одним целым, а меню превратится в нормальный набор продуктов.
Программируем (демо и практика)
Этих знаний достаточно, чтобы начать улучшать собственную программу. Разберём на примере меню с пиццами.
Неадаптированная версия для незрячего выглядит так:
Несколько очевидных проблем, которые предстоит решить:
- Непонятное значение 24 в правом верхнем углу.
- Пустое место слева и сверху.
- Слишком много элементов.
- Неправильное произношение цены («от двести сорок пять знак рубля» вместо «от двести сорок пять рублей»).
Добавляем значения
24 в правом верхнем углу — это количество додо-рублей.
Так и надо подписать:
accessibilityLabel = "Додо-рубли"
accessibilityValue = amountOfDodoRubles
Этот код можно разместить в любом месте, где у вас есть актуальное значение для value.
Для кнопки города можно сделать нечто похожее: лейбл — ваш город, значение — Москва. Но можно и не делать, вроде и так понятно. Не переусердствовать — тоже важная задача.
Убираем пустое место сверху
Акции вверху — это горизонтальный UICollectionView
. Внутри ячейки есть лейбл, именно его находит VoiceOver
.
Как поправить:
- Сделать всю ячейку доступным контролом. По умолчанию все
view
выступают только контейнерами для других элементов,VoiceOver
их игнорирует. Чтобы пометитьview
, как конечный элемент, нужно поставить ячейкеisAccessibilityElement = true
. Это можно сделать в методеawakeFromNib()
. После этого выделяться начнёт вся ячейка, пустое место больше не мешает. - Дать ячейке название. На лейбле больше нельзя сфокусироваться, поэтому нужно вручную указать текст.
accessibilityLabel = specialOffer.title
Настроить можно в методе cellForItemAt
:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let specialOffer = specialOffers[indexPath.row]
let cell = collectionView.deque…
cell.accessibilityLabel = specialOffer.title
return cell
}
Упрощаем табличную ячейку
У ячейки с продуктом две проблемы: много элементов и неправильное описание цены.
Объединяем контролы
Сейчас у ячейки несколько полей: название, описание, цена и картинка, 4 контрола на ячейку. Если в меню 10 продуктов, то это уже 40 маленьких контролов. Надо обобщить, чтобы было 10 продуктов, так получается ближе к смыслу.
Можно упростить:
- Сделать всю ячейку доступным контролом. Это мы уже умеем: ставим ячейке
isAccessibilityElement = true
- В
accessibilityLabel
записать самое важное: название и цену. Разделим запятой,VoiceOver
учитывает пунктуацию. - В
accessibilityValue
указать дополнительную информацию, в нашем случае это состав. - Указать, что ячейку можно нажать, т.е. по сути это кнопка.
accessibilityTraits = .button
Метод внутри ячейки, подставляет нужные значения:
func refreshAccessibility(title: String?,
price: String?,
ingredients: String?,
isProductAvailable: Bool) {
isAccessibilityElement = true (1)
let price = isProductAvailable ? price : "Нет в наличии"
accessibilityLabel = [title, price].compactMap { $0 }.joined(separator: ", ") (2)
accessibilityValue = ingredients (3)
accessibilityTraits = .button
}
Группировать можно не только ячейки, но и все связанные смыслом контролы. Например, переключатель количества и цену стоит обрабатывать вместе: изменили количество — сказали новую цену. Тогда вместо четырёх глупых контролов появится один нормальный.
Склоняем «рубли»
Для правильного написания «рублей» мы генерируем правильную строку и ставим её в accessibilityLabel
для кнопки.
buyButton.accessibilityLabel = String(format: NSLocalizedString("от %d рублей", comment: "Price button. Ex.: от 150 рублей"), price)
Просклонять нужно в файле Localizable.stringsDict
:
Навигация жестами
Для навигации есть два вспомогательных жеста: скрабл и меджик тап.
Скрабл возвращает на предыдущий экран. Чтобы его выполнить, проведите по экрану двумя пальцами, будто пишите букву Z. Ещё скраблом можно заканчивать ввод текста.
Меджик тап вызывает главную функцию текущего экрана. Нужно тапнуть дважды двумя пальцами. Можно включить песню в плеере или ответить на звонок.
У меджик тапа есть UX-проблема — неочевидно, что он делает. Для себя мы решили так: если скрабл переходит на экран назад, то пусть меджик тап переводит на следующий экран по сценарию. На карточке пиццы это добавит её в корзину, если вы были в корзине, то перейдёте на экран доставки, а с доставки переключитесь на оплату.
Но если действие неочевидно, то можно рассказать о меджик тапе в подсказке у кнопки. Но помните: подсказки могут не проговариваться, зависит от настроек.
Адаптируем навигацию
Если нажать на акцию или кнопку додо-рублей, откроется модальный экран. Если бы мы использовали UINavigationController
, то ничего делать было бы не нужно. Но для модальных экранов нужно описать, как они реагируют на дополнительные жесты.
Добавляем скрабл
После того, как пользователь нарисует Z, вызовется метод accessibilityPerformEscape
у firstResponder
. Обычно, это текущий UIViewController
.
Вам достаточно реализовать этот метод, закрыть в нём экран и вернуть true, показав, что жест обработан и можно не проходить responder chain
дальше:
override func accessibilityPerformEscape() -> Bool {
dismiss(animated: true)
return true
}
Похожим образом можно реагировать на меджик тап. Например, применить акцию из карточки:
override func accessibilityPerformMagicTap() -> Bool {
applySpecialOffer()
return true
}
Как находить проблемы
Проблемы адаптации находить несложно, стоит только включить VoiceOver
, как на вас посыпятся десятки. Но спустя время находить новые проблемы станет сложнее, при этом легко пропустить что-то важное, ведь проблемы приходится находить на слух. Есть пара способов упростить жизнь разработчику.
- Включить субтитры. В iOS 13 появилась настройка, которая включает «субтитры»: Settings → Accessibility → VoiceOver → Caption Panel.
- Смотреть подписи через Voice Control. При тестировании VoiceOver можно включить Voice Control, тогда все надписи будут видны сразу. Если где-то цифра, то вы забыли прописать
.accessibilityLabel
. - Accessibility Inspector. Accessibility Inspector даёт возможность посмотреть все
accessibility
свойства в симуляторе. Ещё он может сделать аудит текущего экрана, так вы узнаете о возможных проблемах: малые области нажатия, неконтрастные элементы, неподписанные кнопки. Если нужно, то может прочитать голосом все элементы.
Пока на этом всё
Мы адаптировали один экран. Программировать нужно совсем немного, поддерживать доступность на базовом уровне легко.
Но за кадром осталось многое: разные accessibilityTraits
, набор текста, навигация в приложении, custom actions
, порядок фокусировки, accessibility notifications
, ротор и клавиатуру Брайля. Об этом в следующий раз.
Если уже сейчас хотите узнать больше, то можно почитать:
- Accessibility Guide for iOS.
- Accessibility Programming Guide for OS X.
- Расшифровку выпуска AppsCast про доступность.
- Мой тред в твитере.
- Для веба: книга Inclusive Components
Чтобы не пропустить следующую статью, подписывайтесь на канал Dodo Pizza Mobile.
Автор: Рубанов Михаил