Реалізація Semantic Labels для елементів інтерфейсу мобільного додатка
Semantic Labels — це не просто accessibilityLabel. Це правильно вибудована система описів, підказок, ролей та станів для кожного елемента інтерфейсу, яку screen reader озвучує користувачу. Різниця між «кнопка» (VoiceOver читає щось за замовчуванням) та «Додати в улюблені, кнопка, не активована» — це різниця між додатком, яким можна користуватися, та додатком, яким не можна.
З чого складається повне опис елемента
Label, Hint, Trait, Value
На iOS це чотири окремих властивості UIAccessibilityElement:
- Label — що це: «Фото профілю Івана Петрова», «Кнопка відправити»
- Hint — що станеться при активації (необов'язковий якщо label самодостатній): «Відкриває сторінку профілю»
-
Traits — тип та стан:
.button,.link,.image,.header,.selected,.notEnabled - Value — поточне значення для елементів зі станом: слайдер «гучність 70%», переключувач «включено»
VoiceOver озвучує їх в порядку: label → value → traits → hint. Пауза між label та value, пауза перед hint.
У SwiftUI: .accessibilityLabel(), .accessibilityHint(), .accessibilityAddTraits(), .accessibilityValue().
На Android аналог: contentDescription (= label + value), AccessibilityNodeInfo.setRoleDescription() (= custom trait), stateDescription (API 30+).
Складені елементи
Карточка товару з зображенням, назвою, ціною та кнопкою «В корзину»:
Погано: VoiceOver фокусується на кожному subview окремо — 4 кроки, щоб дісватися кнопки. Screen reader читає: «зображення», «Nike Air Max 90», «8 900 гривень», «кнопка».
Правильно: об'єднати в один елемент з складним label: «Nike Air Max 90, 8 900 гривень» + trait .button + hint «Додає в корзину». Плюс окремий елемент — кнопка «В корзину» — якщо користувачу потрібен швидкий доступ до неї без прочитання всієї карточки.
У UIKit: containerView.accessibilityElements = [productAccessibilityElement, addToCartButton]. productAccessibilityElement — кастомний UIAccessibilityElement з потрібним accessibilityFrame та label з кількох полів.
Динамічні стани
Кнопка «Улюблене» — іконка серцечка, без тексту, змінює стан. Базовий label «Додати в улюблене». При додаванні — потрібно оновити:
- iOS:
accessibilityLabel = "Видалити з улюблених", або черезaccessibilityTraits.insert(.selected)+ label «Улюблене» (VoiceOver додасть «обрано») - Після зміни:
UIAccessibility.post(notification: .announcement, argument: "Додано в улюблені")— миттєве повідомлення без переспрямування фокуса
Переключувачі (UISwitch, Toggle у SwiftUI, Switch у Compose) — стан озвучується автоматично: «Сповіщення, включено». Для кастомних toggle — потрібно встановлювати accessibilityValue = isOn ? "включено" : "вимкнено".
Групи-заголовки
Екран з кількома секціями: accessibilityTraits = .header у заголовку секції — користувач VoiceOver може переміщатися між заголовками свайпом з вибором «Headings» у accessibility rotor. Без цього неможливо швидко перейти до потрібної секції без лінійного обходу всіх елементів.
У Compose: Modifier.semantics { heading() } у Text-заголовку.
Типові помилки
Іконки кнопок — contentDescription = "ic_heart" (назва файлу ресурсу) замість «Додати в улюблене». Android Studio попереджує, але розробники часто залишають як є.
Placeholder у TextField як label: hint = "Введіть email" в Android EditText — TalkBack прочитає hint як contentDescription лише якщо contentDescription не встановлений. При фокусировке hint зникає та опис пропадає. Потрібен явний contentDescription або використання TextInputLayout з плаваючим hint.
Кнопки з числовими бейджами: «3» рядом з іконкою дзвіночка — screen reader читає «3, кнопка» без контексту. Потрібно: accessibilityLabel = "Сповіщення, 3 непрочитаних".
Термін: 1-3 дні залежно від кількості компонентів. Часто совмещается з VoiceOver/TalkBack аудитом. Вартість розраховується індивідуально.







