Почему Accessibility — это не просто галочка, а суперсила вашего приложения
Представьте: вы создали крутое приложение. Красивый интерфейс, молниеносная анимация, идеальный код на Swift. Вы гордитесь им. Но вдруг оказывается, что 15% пользователей не могут им пользоваться. Для них кнопки слишком мелкие, текст сливается с фоном, а VoiceOver «читает» кашу вместо меню. Знакомо?
Accessibility (доступность) — это не про «обязаловку» и не про «сделаем для галочки». Это про инклюзивность, про заботу и, что немаловажно, про бизнес. В iOS есть мощнейший фреймворк, который позволяет адаптировать приложение под нужды людей с нарушениями зрения, слуха, моторики и когнитивными особенностями. И Swift даёт нам все инструменты, чтобы сделать это элегантно.
В этой статье мы не будем читать скучные лекции. Мы разберём конкретные примеры кода, которые можно сразу внедрить в проект. Поехали!
VoiceOver: как «научить» приложение рассказывать о себе
VoiceOver — это экранный диктор, встроенный в iOS. Он «озвучивает» всё, что происходит на экране: текст, кнопки, жесты. Но если разработчик не подготовил интерфейс, VoiceOver может превратиться в хаос. Давайте исправим это.
Базовая настройка: accessibilityLabel и accessibilityTraits
Самый простой способ помочь VoiceOver — задать метки и типы элементов. Например, у вас есть кнопка с иконкой сердца. Без метки VoiceOver скажет просто «кнопка», а с меткой — «Добавить в избранное».
let favoriteButton = UIButton(type: .system)
favoriteButton.setImage(UIImage(systemName: "heart"), for: .normal)
favoriteButton.accessibilityLabel = "Добавить в избранное"
favoriteButton.accessibilityTraits = .button
Обратите внимание на accessibilityTraits. Это подсказка для VoiceOver, как взаимодействовать с элементом. Для кнопки — .button, для изображения — .image, для заголовка — .header. Используйте .selected, .notEnabled и другие, чтобы точно передать состояние.
Группировка элементов: как не дать VoiceOver «заболтать» пользователя
Иногда логично объединить несколько элементов в один смысловой блок. Например, карточка товара: изображение, название, цена. Если VoiceOver будет читать их по отдельности, пользователь устанет. Решение — UIAccessibilityElement или shouldGroupAccessibilityChildren.
cardView.shouldGroupAccessibilityChildren = true
cardView.accessibilityLabel = "Кроссовки Nike Air, 12 990 рублей"
cardView.accessibilityTraits = .button
Теперь VoiceOver прочитает всю карточку как один элемент. Кратко, ёмко, понятно.
Кастомные действия: когда нужно больше, чем просто «тап»
Бывают сложные элементы: слайдеры, переключатели, список смахиваний. Для них важно добавить кастомные действия. Например, для ячейки с функцией «Удалить» и «Редактировать»:
let deleteAction = UIAccessibilityCustomAction(
name: "Удалить",
target: self,
selector: #selector(deleteItem)
)
let editAction = UIAccessibilityCustomAction(
name: "Редактировать",
target: self,
selector: #selector(editItem)
)
cell.accessibilityCustomActions = [deleteAction, editAction]
Пользователь VoiceOver смахивает вниз или вверх, чтобы выбрать действие. Это значительно улучшает опыт.
Dynamic Type: текст, который подстраивается под каждого
Люди с ослабленным зрением часто увеличивают шрифт в настройках iOS. Если ваше приложение не поддерживает Dynamic Type, текст обрежется, наложится друг на друга или просто станет нечитаемым. Как этого избежать?
Используем динамические стили шрифтов
Вместо жёсткого UIFont.systemFont(ofSize: 16) используйте UIFont.preferredFont(forTextStyle: .body).
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
adjustsFontForContentSizeCategory — волшебное свойство, которое заставляет шрифт автоматически реагировать на изменения размера текста в системе. Не забудьте включить его для всех UILabel, UITextField и UITextView.
Работа с Auto Layout и масштабированием
Когда пользователь увеличивает шрифт, элементы интерфейса должны растягиваться, а не ломаться. Используйте UIStackView и относительные констрейнты. Избегайте фиксированной высоты для кнопок и лейблов.
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 8
stackView.distribution = .fill
let titleLabel = UILabel()
titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
titleLabel.adjustsFontForContentSizeCategory = true
titleLabel.numberOfLines = 0 // Разрешаем перенос строк
stackView.addArrangedSubview(titleLabel)
Обратите внимание на numberOfLines = 0. Это разрешает тексту занимать столько строк, сколько нужно. Без этого при большом шрифте текст обрежется.
Управление фокусом: как не потерять пользователя в навигации
VoiceOver перемещается по элементам в порядке, определённом accessibilityElements. По умолчанию это порядок добавления в иерархию. Но что, если нужно изменить логику?
Ручная настройка порядка фокуса
view.accessibilityElements = [firstElement, secondElement, thirdElement]
Это особенно полезно в сложных экранах: формах, модальных окнах, кастомных таблицах. Не заставляйте пользователя тыкать вслепую — проложите чёткий маршрут.
Программный перевод фокуса
Бывают ситуации, когда нужно перевести фокус на конкретный элемент. Например, после отправки формы — на сообщение об ошибке.
UIAccessibility.post(notification: .screenChanged, argument: errorLabel)
Но используйте эту возможность аккуратно. Частые «перескоки» фокуса дезориентируют пользователя. Лучше один раз чётко направить, чем дёргать каждую секунду.
Цветовой контраст и «умные» жесты: детали, которые меняют всё
Accessibility — это не только VoiceOver. Это ещё и визуальная доступность.
Контрастность текста и иконок
Apple рекомендует коэффициент контрастности не менее 4.5:1 для обычного текста и 3:1 для крупного. Проверить это можно в Xcode через Accessibility Inspector. Не надейтесь на глаз — используйте инструменты.
Также учитывайте UIAccessibility.isReduceTransparencyEnabled и UIAccessibility.isReduceMotionEnabled. Если пользователь включил уменьшение прозрачности — убирайте размытые фоны. Если уменьшение движения — отключайте параллакс и анима