Accessibility Trait — это свойство элемента iOS, которое определяет его роль и поведение для VoiceOver. Трейт сообщает screen reader, как элемент должен быть озвучен и какие жесты доступны: является ли он кнопкой, заголовком, ссылкой или полем поиска. По данным Apple UIAccessibilityTraits, 2024, система поддерживает 15+ констант, которые можно комбинировать побитовой маской. Правильно выбранный трейт экономит до 50% времени навигации для пользователей VoiceOver.
Главное
Accessibility Trait — флаг, устанавливаемый на элементе UIView для указания его семантической роли VoiceOver. Трейт — один из трёх компонентов accessibility-триады Apple: Label (имя), Hint (описание), Trait (роль). iOS использует битовую маску UIAccessibilityTraits (UInt64), где каждый бит соответствует определённой роли. VoiceOver читает роль после Label и Hint: «Кнопка Отправить. Откроет форму» — «Кнопка» добавлена благодаря трейту UIAccessibilityTraitButton.
По умолчанию UIButton получает UIAccessibilityTraitButton, UILabel — UIAccessibilityTraitStaticText, UIImageView — UIAccessibilityTraitImage. При использовании кастомных контролов разработчик обязан установить трейт вручную. Apple Human Interface Guidelines, 2024, называют это «одним из самых критичных шагов в обеспечении accessibility».
Без правильного трейта пользователь не знает, какой жест применить: одинарное касание (активация кнопки), двойное касание (увеличение) или жест смахивания (переключатель). Трейт решает, какие жесты VoiceOver активирует на элементе.
UIAccessibilityTraits — это typealias UInt64. Каждый трейт — константа, где установлен ровно один бит. Например, UIAccessibilityTraitButton = 0x0000000000000001, UIAccessibilityTraitLink = 0x0000000000000002, UIAccessibilityTraitHeader = 0x0000000000000008. Комбинация достигается побитовым ИЛИ: 0x0001 | 0x0008 = 0x0009. VoiceOver анализирует маску и определяет поведение.
iOS предоставляет более 15 констант трейтов. Рассмотрим основные, используемые в 90% сценариев:
| Трейт | Константа | Поведение VoiceOver |
|---|---|---|
| Button | UIAccessibilityTraitButton | Активация через двойное касание |
| Header | UIAccessibilityTraitHeader | Быстрая навигация по заголовкам |
| Link | UIAccessibilityTraitLink | Активация как ссылка |
| StaticText | UIAccessibilityTraitStaticText | Только чтение, без активации |
| SearchField | UIAccessibilityTraitSearchField | Поле поиска с особым поведением |
| Image | UIAccessibilityTraitImage | Изображение, без жеста активации |
| Selected | UIAccessibilityTraitSelected | Состояние «выбран» |
| PlaysSound | UIAccessibilityTraitPlaysSound | Воспроизводит звук при активации |
| KeyboardKey | UIAccessibilityTraitKeyboardKey | Клавиша клавиатуры |
| TabBar | UIAccessibilityTraitTabBar | Элемент таб-бара |
Константы доступны в UIKit с iOS 3.0. В iOS 14+ добавлена поддержка UIAccessibilityTraits в SwiftUI через модификатор .accessibilityAddTraits().
UIAccessibilityTraitAdjustable — для регулируемых значений (ползунки, пикеры, Volume-слайдеры). VoiceOver позволяет проводить вверх/вниз для изменения значения с шагом, определённым через accessibilityIncrement и accessibilityDecrement. UIAccessibilityTraitUpdatesFrequently — для элементов с часто меняющимся значением (таймер, индикатор загрузки). VoiceOver не зачитывает значение при каждом изменении, а берёт паузу. UIAccessibilityTraitAllowsDirectInteraction — для элементов, с которыми пользователь может взаимодействовать напрямую (клавиатура, рисовалка), минуя VoiceOver-жесты.
Один элемент может иметь несколько трейтов одновременно — комбинация задаётся через побитовое ИЛИ (|). Пример: кнопка, которая сейчас выбрана — Button | Selected. VoiceOver произнесёт: «Выбрано. Отфильтровано по цене. Кнопка».
Установка трейтов в коде:
filterButton.accessibilityTraits.insert(.button)
filterButton.accessibilityTraits.insert(.selected)
// Или через маску:
filterButton.accessibilityTraits = [.button, .selected]
Для кастомных UIView, где трейт не установлен по умолчанию:
class CustomToggle: UIControl {
override var accessibilityTraits: UIAccessibilityTraits {
get {
if isOn {
return [.button, .selected]
} else {
return .button
}
}
set {}
}
}
Правило комбинирования: не более 3-4 трейтов на элемент. Избыточные трейты (например, Button + Link + Header) делают объявление VoiceOver слишком длинным и запутанным. По данным Apple, «каждое дополнительное свойство увеличивает когнитивную нагрузку на пользователя».
В SwiftUI трейты задаются через модификаторы .accessibilityAddTraits() и .accessibilityRemoveTraits(). Пример: Text("Заголовок").font(.largeTitle).accessibilityAddTraits(.isHeader). Модификатор .isHeader добавляет UIAccessibilityTraitHeader. Список трейтов SwiftUI: .isButton, .isHeader, .isLink, .isSelected, .isImage, .isSearchField, .isKeyboardKey, .isStaticText, .isSummaryElement, .isToggle, .playsSound, .startsMediaSession, .updatesFrequently, .allowsDirectInteraction, .causesPageTurn, .isModal, .tabBar.
StaticText вместо Button — кастомный контрол, визуально выглядящий как кнопка, получает трейт StaticText по умолчанию. VoiceOver не предлагает жест активации, пользователь не может «нажать» элемент. Решение: явно установить .button.
Image без трейта — UIImageView с включённым accessibility получает трейт Image, даже если на самом деле это кнопка для увеличения фото. Назначьте .button и Label «Увеличить фото». По данным WWDC 2023, «Deliver an Exceptional Accessibility Experience», 40% accessibility-регрессий в новых версиях приложений вызваны именно несоответствием трейта.
Header на каждом элементе — трейт Header предназначен для структурных заголовков экрана. Если сделать каждый UILabel заголовком, ротор VoiceOver с режимом «Заголовки» станет бесполезным — он будет останавливаться на каждом слове.
Частая причина потери трейта — рефакторинг: разработчик заменяет UIButton на UIControl для кастомного отображения. UIButton автоматически получает трейт Button, UIControl — нет. После рефакторинга нужно явно задать accessibilityTraits = .button. Добавьте проверку в код-ревью: «Если заменили UIButton на UIControl — проверьте трейт».
Для элементов с изменяющимся состоянием (например, кнопка лайка) трейт должен меняться динамически. В состоянии «не лайкнуто» — Button, в состоянии «лайкнуто» — Button + Selected + Image (если иконка). VoiceOver меняет объявление: «Нравится. Кнопка» vs «Выбрано. Нравится. Кнопка». Используйте accessibilityValue для передачи состояния, если трейт Selected недостаточен. Актуально для кнопок подписки, избранного, фильтров и переключателей.
В Android прямой аналогии трейтам нет. Вместо битовой маски используются:
Для кастомных View в Android нужно переопределить onInitializeAccessibilityNodeInfo:
class CustomButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
override fun onInitializeAccessibilityNodeInfo(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(info)
info.className = "android.widget.Button"
info.isClickable = true
}
}
Разработчикам Flutter нужно использовать параметр semanticsRole в виджете Semantics: button, header, image, link, textField и другие. Дополнительно доступно semanticsLabel и semanticsHint — полный аналог iOS-триады Label + Hint + Trait.
Для веб-версий мобильных приложений (PWA, WebView) используется атрибут role из WAI-ARIA: role="button", role="heading", role="link". Это прямой аналог accessibilityTraits. В гибридных приложениях проверяйте, что WebView передаёт ARIA-роли в нативный accessibility-слой. Для этого используйте протокол UIAccessibilityContainerDataTable в iOS или setAccessibilityDelegate в Android. WebView с включённым JavaScript может не передавать ARIA-роли корректно — тестируйте отдельно.
В Android можно добавить кастомные действия в AccessibilityNodeInfo: AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK и ACTION_LONG_CLICK. Это аналог трейта Button с дополнительными жестами. Для слайдеров используйте ACTION_SET_PROGRESS — аналог Adjustable. Для Spinner и DatePicker — ACTION_SET_SELECTION, ACTION_SET_DATE и ACTION_SET_TIME.
Xcode Accessibility Inspector — основной инструмент для iOS: выберите элемент и просмотрите поле Traits. Оно покажет список установленных трейтов. VoiceOver-ротор с режимом «Элементы» позволяет пройтись по всем контролам экрана.
Автоматизированный тест на Swift для проверки трейта:
func testSubmitButtonTrait() {
let app = XCUIApplication()
app.launch()
let submitButton = app.buttons["Отправить"]
XCTAssertTrue(submitButton.isEnabled)
// XCUIElement не предощает прямой доступ к трейтам
// Проверка через активацию жеста
submitButton.tap()
XCTAssertTrue(app.staticTexts["Форма отправлена"].exists)
}
Ручная проверка через VoiceOver: включите VoiceOver, проведите пальцем до элемента, коснитесь дважды — элемент должен активироваться, если это Button. Если элемент не реагирует на двойное касание, трейт указан неверно. Используйте жест Rotor для переключения между режимами («Заголовки», «Ссылки», «Кнопки») — каждый режим покажет только элементы с соответствующим трейтом.
До iOS 14 unit-тесты не имели прямого доступа к accessibilityTraits. Начиная с iOS 14, свойство доступно: XCTAssertEqual(customButton.accessibilityTraits, .button). Используйте это в модульных тестах для проверки кастомных контролов. Рекомендуется тестировать каждый новый кастомный UIView на корректность трейта, особенно после рефакторинга или смены родительского класса.
Часто задаваемые вопросы
До 3-4 трейтов на элемент. Большее количество делает объявление VoiceOver избыточным. Используйте комбинации: Button + Selected, Header + StaticText.
UIAccessibilityTraitButton. iOS автоматически устанавливает его для всех экземпляров UIButton. Если вы наследуетесь от UIView и имитируете кнопку, трейт нужно установить вручную.
Да, UIAccessibilityTraitAdjustable — для элементов с регулируемым значением (ползунки, пикеры, счётчики). VoiceOver позволяет проводить вверх/вниз для изменения значения и читает текущее состояние.
Используйте модификатор .accessibilityAddTraits(): Text("Заголовок").font(.title).accessibilityAddTraits(.isHeader). Метод работает на iOS 14+.
VoiceOver присвоит трейт None. Элемент не получит роль — screen reader прочитает только Label без указания типа. Пользователь не узнает, доступен ли жест активации.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также