Accessibility Trait: същност, какви видове има и как работят в разработката

Автор: IT Sectr Публикувано: 2026-05-16 Време за четене: 9 мин

Accessibility Trait — свойство на iOS елемент, което определя неговата роля и поведение за VoiceOver. Траитът информира екранния четец как елементът трябва да бъде озвучен и какви жестове са налични: дали е бутон, заглавие, връзка или поле за търсене. Според 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 — за регулируеми стойности (плъзгачи, избирачи, плъзгачи за сила на звука). 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. Добавете проверка в code review: «Ако сте заменили UIButton с UIControl — проверете траита».

Траитове и динамични състояния

За елементи с променящо се състояние (например бутон за харесване) траитът трябва да се променя динамично. В състояние «нехаресано» — Button, в състояние «харесано» — Button + Selected + Image (ако има икона). VoiceOver променя съобщението: «Харесвам. Бутон» срещу «Избрано. Харесвам. Бутон». Използвайте 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.

Уеб аналози: 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. Елементът няма да получи роля — екранният четец ще прочете само 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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също