Accessibility Trait: суть, види та як працюють у розробці

Автор: IT Sectr Опубліковано: 2026-05-16 Час читання: 9 хв

Accessibility Trait — це властивість елемента iOS, яка визначає його роль і поведінку для VoiceOver. Трейт повідомляє screen reader, як елемент має бути озвучений і які жести доступні: чи є він кнопкою, заголовком, посиланням або полем пошуку. За даними Apple UIAccessibilityTraits, 2024, система підтримує 15+ констант, які можна комбінувати побітовою маскою. Правильно вибраний трейт економить до 50% часу навігації для користувачів VoiceOver.

Головне

  • Accessibility Trait — роль елемента iOS для VoiceOver; задається через константи UIAccessibilityTraits
  • Трейти можна комбінувати через оператор | для створення складних ролей (кнопка + вибрано)
  • Кожен елемент може мати кілька трейтів одночасно, але не більше 3-4 для уникнення плутанини
  • Неправильний трейт (наприклад, StaticText для кнопки) ламає сценарій взаємодії: користувач не знає, чи доступний жест
  • В Android аналог — атрибути role та className в AccessibilityNodeInfo

Що таке Accessibility Trait

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

UIAccessibilityTraits — це typealias UInt64. Кожен трейт — константа, де встановлено рівно один біт. Наприклад, UIAccessibilityTraitButton = 0x0000000000000001, UIAccessibilityTraitLink = 0x0000000000000002, UIAccessibilityTraitHeader = 0x0000000000000008. Комбінація досягається побітовим АБО: 0x0001 | 0x0008 = 0x0009. VoiceOver аналізує маску та визначає поведінку.

Основні типи трейтів iOS

iOS надає понад 15 констант трейтів. Розглянемо основні, що використовуються в 90% сценаріїв:

ТрейтКонстантаПоведінка VoiceOver
ButtonUIAccessibilityTraitButtonАктивація через подвійне торкання
HeaderUIAccessibilityTraitHeaderШвидка навігація за заголовками
LinkUIAccessibilityTraitLinkАктивація як посилання
StaticTextUIAccessibilityTraitStaticTextЛише читання, без активації
SearchFieldUIAccessibilityTraitSearchFieldПоле пошуку з особливою поведінкою
ImageUIAccessibilityTraitImageЗображення, без жесту активації
SelectedUIAccessibilityTraitSelectedСтан «вибрано»
PlaysSoundUIAccessibilityTraitPlaysSoundВідтворює звук при активації
KeyboardKeyUIAccessibilityTraitKeyboardKeyКлавіша клавіатури
TabBarUIAccessibilityTraitTabBarЕлемент таб-бару

Константи доступні в UIKit з iOS 3.0. В iOS 14+ додано підтримку UIAccessibilityTraits в SwiftUI через модифікатор .accessibilityAddTraits().

Рідкісні, але корисні трейти

UIAccessibilityTraitAdjustable — для регульованих значень (повзунки, пікери, Volume-слайдери). VoiceOver дозволяє проводити вгору/вниз для зміни значення з кроком, визначеним через accessibilityIncrement та accessibilityDecrement. UIAccessibilityTraitUpdatesFrequently — для елементів із часто змінюваним значенням (таймер, індикатор завантаження). VoiceOver не зачитує значення при кожній зміні, а бере паузу. UIAccessibilityTraitAllowsDirectInteraction — для елементів, з якими користувач може взаємодіяти безпосередньо (клавіатура, малювання), минаючи VoiceOver-жести.

Комбінування трейтів

Один елемент може мати кілька трейтів одночасно — комбінація задається через побітове АБО (|). Приклад: кнопка, яка зараз вибрана — Button | Selected. VoiceOver вимовить: «Вибрано. Відфільтровано за ціною. Кнопка».

Встановлення трейтів у коді:

swift
filterButton.accessibilityTraits.insert(.button)
filterButton.accessibilityTraits.insert(.selected)

// Або через маску:
filterButton.accessibilityTraits = [.button, .selected]

Для кастомних UIView, де трейт не встановлено за замовчуванням:

swift
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: модифікатори трейтів

В 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 з режимом «Заголовки» стане марним — він зупинятиметься на кожному слові.

Як виправити: чек-лист

  • Кожен інтерактивний кастомний елемент отримує трейт Button, Link або Adjustable
  • Заголовки секцій отримують трейт Header (не StaticText)
  • Зображення-кнопки отримують трейт Button + Selected при стані selected
  • Елементи без жесту — StaticText або Image (лише читання)

Регресійні баги при заміні UIButton на UIControl

Часта причина втрати трейту — рефакторинг: розробник замінює UIButton на UIControl для кастомного відображення. UIButton автоматично отримує трейт Button, UIControl — ні. Після рефакторингу потрібно явно задати accessibilityTraits = .button. Додайте перевірку в код-рев'ю: «Якщо замінили UIButton на UIControl — перевірте трейт».

Трейти та динамічні стани

Для елементів зі змінним станом (наприклад, кнопка лайку) трейт повинен змінюватися динамічно. В стані «не лайкнуто» — Button, в стані «лайкнуто» — Button + Selected + Image (якщо є іконка). VoiceOver змінює оголошення: «Подобається. Кнопка» vs «Вибрано. Подобається. Кнопка». Використовуйте accessibilityValue для передачі стану, якщо трейт Selected недостатній. Актуально для кнопок підписки, обраного, фільтрів та перемикачів.

Android-аналог: role та className

В Android прямої аналогії трейтам немає. Замість бітової маски використовуються:

  • className — значення AccessibilityNodeInfo.className (android.widget.Button, android.widget.TextView)
  • role — атрибут в XML (роль визначається типом View)
  • stateDescription — аналог Selected: додавання опису стану (увімкнено/вимкнено)

Для кастомних View в Android потрібно перевизначити onInitializeAccessibilityNodeInfo:

kotlin
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.

Web-аналоги: WAI-ARIA role

Для веб-версій мобільних додатків (PWA, WebView) використовується атрибут role з WAI-ARIA: role="button", role="heading", role="link". Це прямий аналог accessibilityTraits. В гібридних додатках перевіряйте, що WebView передає ARIA-ролі в нативний accessibility-шар. Для цього використовуйте протокол UIAccessibilityContainerDataTable в iOS або setAccessibilityDelegate в Android. WebView з увімкненим JavaScript може не передавати ARIA-ролі коректно — тестуйте окремо.

AccessibilityNodeInfo: додаткові дії

В 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 для перевірки трейту:

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 для перемикання між режимами («Заголовки», «Посилання», «Кнопки») — кожен режим покаже лише елементи з відповідним трейтом.

Unit-тестування трейтів в iOS

До iOS 14 unit-тести не мали прямого доступу до accessibilityTraits. Починаючи з iOS 14, властивість доступна: XCTAssertEqual(customButton.accessibilityTraits, .button). Використовуйте це в модульних тестах для перевірки кастомних контролів. Рекомендується тестувати кожен новий кастомний UIView на коректність трейту, особливо після рефакторингу або зміни батьківського класу.

Часто задавані питання

Скільки трейтів можна задати одному елементу?

До 3-4 трейтів на елемент. Більша кількість робить оголошення VoiceOver надлишковим. Використовуйте комбінації: Button + Selected, Header + StaticText.

Який трейт у UIButton за замовчуванням?

UIAccessibilityTraitButton. iOS автоматично встановлює його для всіх екземплярів UIButton. Якщо ви успадковуєтеся від UIView та імітуєте кнопку, трейт потрібно встановити вручну.

Чи є трейт «Adjustable» і для чого він?

Так, UIAccessibilityTraitAdjustable — для елементів з регульованим значенням (повзунки, пікери, лічильники). VoiceOver дозволяє проводити вгору/вниз для зміни значення та читає поточний стан.

Як перевірити трейти в SwiftUI?

Використовуйте модифікатор .accessibilityAddTraits(): Text(«Заголовок»).font(.title).accessibilityAddTraits(.isHeader). Метод працює на iOS 14+.

Що буде, якщо не задати трейт кастомному контролу?

VoiceOver присвоїть трейт None. Елемент не отримає роль — screen reader прочитає лише Label без вказівки типу. Користувач не дізнається, чи доступний жест активації.

Підсумки

  • Accessibility Trait — бітова маска UIAccessibilityTraits, що визначає роль елемента iOS для VoiceOver (Button, Header, Link, StaticText та інші)
  • Трейти комбінуються через побітове АБО ([] в Swift), не більше 3-4 на елемент
  • Кастомні UIView зобов'язані отримувати явний трейт — за замовчуванням може бути None або Image
  • В Android роль задається через className в AccessibilityNodeInfo, в Flutter — через semanticsRole
  • Неправильний трейт (StaticText для кнопки) ламає сценарій VoiceOver: немає жесту активації
  • Перевіряйте трейти через Accessibility Inspector в Xcode та VoiceOver-ротор
  • В SwiftUI використовуйте .accessibilityAddTraits() для налаштування трейтів декларативно

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також