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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також