Accessibility Trait — свойство на iOS елемент, което определя неговата роля и поведение за VoiceOver. Траитът информира екранния четец как елементът трябва да бъде озвучен и какви жестове са налични: дали е бутон, заглавие, връзка или поле за търсене. Според 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 — за регулируеми стойности (плъзгачи, избирачи, плъзгачи за сила на звука). 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. Добавете проверка в code review: «Ако сте заменили UIButton с UIControl — проверете траита».
За елементи с променящо се състояние (например бутон за харесване) траитът трябва да се променя динамично. В състояние «нехаресано» — Button, в състояние «харесано» — Button + Selected + Image (ако има икона). VoiceOver променя съобщението: «Харесвам. Бутон» срещу «Избрано. Харесвам. Бутон». Използвайте 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. Елементът няма да получи роля — екранният четец ще прочете само Label без посочване на типа. Потребителят няма да знае дали жестът за активиране е наличен.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също