Focus Order — это последовательность, в которой элементы интерфейса получают фокус при навигации с помощью клавиатуры, Switch Control, VoiceOver или TalkBack. В мобильных приложениях порядок фокуса определяет, как пользователь перемещается между контролами жестами или кнопками. По данным W3C WCAG 2.2, Success Criterion 2.4.3, 2023, фокус должен следовать в логическом порядке, сохраняющем смысл контента. Нарушение этого принципа — одна из частых причин непроходимости accessibility-аудита.
Главное
Focus Order — это последовательность, в которой пользователь перемещается между интерактивными элементами с помощью альтернативных методов ввода: клавиатуры (Tab), Switch Control (шаг за шагом), VoiceOver (жест вправо/влево) или TalkBack. В отличие от мыши или сенсорного экрана, где пользователь выбирает элемент напрямую, фокусная навигация линейна — каждый шаг перемещает фокус на следующий элемент.
По данным Apple HIG, 2024, VoiceOver использует порядок элементов в дереве accessibility, которое строится на основе визуального расположения: левый верхний угол → правый нижний. Если экран содержит сложную вёрстку (колонки, Grid, ZStack), дерево может не соответствовать визуальному порядку.
Принцип WCAG 2.4.3: «Если веб-страницу можно последовательно перемещать по секциям и порядок фокуса влияет на смысл, то фокус должен следовать в порядке, сохраняющем смысл и возможность управления». Исключение: динамический контент, где фокус может прыгать для привлечения внимания (предупреждения, модальные окна).
Пользователь Switch Control (люди с моторными нарушениями) перемещается по элементам автоматически — цикл за циклом. Если порядок нарушен, пользователь тратит в 3 раза больше времени на завершение формы. По данным Deque University, 2024, корректный Focus Order сокращает время заполнения формы на 60% для пользователей вспомогательных технологий.
Особое внимание — модальным окнам. После открытия модального окна фокус должен немедленно переместиться на первый интерактивный элемент внутри модалки (обычно кнопка «Закрыть» или «Подтвердить»). После закрытия — вернуться на элемент, который вызвал модальное окно. Это требование WCAG 2.4.3 и одновременно распространённая ошибка.
В iOS VoiceOver автоматически строит порядок на основе геометрии: элементы сортируются по Y, затем по X. Для экранов со сложной структурой этот порядок может быть некорректным — разработчик должен вмешаться.
Основные инструменты:
Пример задания кастомного порядка для карточки товара:
class ProductCardView: UIView {
let titleLabel = UILabel()
let priceLabel = UILabel()
let buyButton = UIButton()
override var accessibilityElements: [Any]? {
get {
return [titleLabel!, priceLabel!, buyButton!]
}
set {}
}
}
Для программного перемещения фокуса после действия:
UIAccessibility.post(
notification: .layoutChanged,
argument: newlyAddedItem
)
Свойство shouldGroupAccessibilityElement полезно для карточек в коллекциях. Если установить true на родительской карточке, VoiceOver воспринимает всю карточку как один элемент. Пользователь может дважды коснуться, чтобы активировать карточку целиком, или настроить ротор для навигации внутри. Рекомендуется для UICollectionViewCell и UITableViewCell.
В Android TalkBack также использует геометрический порядок, но приоритет отдаётся явным атрибутам nextFocus*. Эти атрибуты задаются в XML или программно:
| Атрибут | Назначение | Пример |
|---|---|---|
| nextFocusDown | Элемент при навигации вниз | @+id/field_email |
| nextFocusUp | Элемент при навигации вверх | @+id/field_name |
| nextFocusLeft | Элемент слева | @+id/btn_back |
| nextFocusRight | Элемент справа | @+id/btn_next |
Пример для формы регистрации:
<EditText
android:id="@+id/field_email"
android:nextFocusDown="@+id/field_password" />
<EditText
android:id="@+id/field_password"
android:nextFocusDown="@+id/btn_submit" />
Для RecyclerView порядок фокуса динамический — определяется адаптером. Если ячейки имеют сложную структуру, установите descendantFocusability = "beforeDescendants" и задайте порядок в узле элемента списка. Для Jetpack Compose порядок фокуса задаётся через Modifier.focusOrder() и FocusOrder. Приоритет: previous (дочерний), next (следующий), custom key.
Если элемент слишком мал для фокуса (меньше 44pt), увеличьте hit area через TouchDelegate в iOS или minWidth/minHeight в Android. По данным Google Material Design, 2024, минимальная область касания — 48×48dp. VoiceOver и TalkBack фокусируются на bounding box элемента. Элементы размером менее 30pt могут быть недоступны для жестового фокуса — пользователь физически не может попасть по ним пальцем.
Прыгающий фокус — когда после акции (например, удаления элемента) фокус перемещается в начало списка или на системную кнопку «Назад». Пользователь VoiceOver теряет контекст. Решение: программно перемещать фокус на элемент, ближайший к удалённому.
Невидимый фокус — элемент получает фокус, но визуального индикатора нет (пользователи клавиатуры не видят, где находятся). В iOS проверяйте UIAccessibility.isVoiceOverRunning для кастомных индикаторов. По данным Deque University, 2024, невидимый фокус — вторая по частоте причина провала accessibility-аудита.
Модальные окна — фокус остаётся на фоновом контенте после открытия модального окна. В iOS модальный view автоматически захватывает фокус, если установлен modalPresentationStyle = .pageSheet. В Android используйте setFocusable(true) на контейнере диалога.
Обратная проблема: фокус застревает внутри модального окна и не может выйти (кроме закрытия). Это допустимо только для модальных окон — пользователь должен сознательно закрыть окно. Для обычных экранов focus trap — критическая ошибка. Решение: убедитесь, что последний элемент модального окна (кнопка «Закрыть») передаёт фокус обратно.
Для кастомных экранов (карты, канвасы, игры) автоматический геометрический порядок неприменим. Разработчик должен построить дерево accessibility вручную. В iOS для этого переопределяется метод UIAccessibilityContainer.
Пример для кастомного канваса:
class CanvasView: UIView {
var shapes: [ShapeView] = []
override var accessibilityElements: [Any]? {
get {
// Сортируем фигуры по Z-индексу, а не по геометрии
return shapes.sorted { $0.zIndex < $1.zIndex }
}
set {}
}
}
В Android для кастомной View переопределите onInitializeAccessibilityNodeInfo:
override fun onInitializeAccessibilityNodeInfo(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(info)
info.addChild(firstElement)
info.addChild(secondElement)
info.isFocusable = true
}
Для динамических списков (чат, лента новостей) после добавления элемента вызывайте перемещение фокуса на первый новый элемент. В iOS: UIAccessibility.post(notification: .layoutChanged, argument: newMessage). В Android: sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED).
iOS автоматически определяет область фокуса на основе frame элемента. Если элемент имеет трансформацию (transform, rotation), VoiceOver может фокусироваться на неправильной области. Явно задайте accessibilityFrame в координатах экрана: element.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(element.bounds, in: element). Это гарантирует, что VoiceOver подсветит правильную область.
Для анимированных экранов (UIKit Dynamics, Lottie, SpriteKit) программный фокус особенно важен. VoiceOver не может построить дерево accessibility для динамически движущихся элементов. Устанавливайте isAccessibilityElement = false на контейнерах анимации и true только на интерактивных элементах внутри.
Ручное тестирование: включите VoiceOver (iOS) или TalkBack (Android), проведите жестом вправо по всей последовательности. Фокус должен следовать визуальному порядку — слева направо, сверху вниз. Каждый интерактивный элемент должен получить фокус ровно один раз.
Автоматизированное тестирование затруднено, но возможно:
func testKeyboardFocusOrder() {
let app = XCUIApplication()
app.launch()
app.textFields["Email"].tap()
// Tab — только с аппаратной клавиатурой
}
Для Android используйте Accessibility Testing Framework:
@Test
fun testFocusOrder() {
onView(withId(R.id.fieldEmail))
.check(matches(isFocusable()))
onView(withId(R.id.fieldEmail))
.perform(focus())
onView(withId(R.id.fieldPassword))
.check(matches(isFocused()))
}
Наиболее надёжный метод — UI-тест сценария: заполните форму пошагово (Email → Пароль → Отправить), проверяя, что каждый шаг завершается успешно. Если порядок фокуса нарушен, сценарий упадёт на попытке взаимодействия с элементом вне фокуса.
Инструмент Accessibility Inspector в Xcode показывает полное дерево accessibility. Вы можете пройти по элементам в порядке VoiceOver и увидеть точный фокусный путь. Используйте вкладку «Audit» для автоматического поиска нарушений Focus Order.
Часто задаваемые вопросы
WCAG 2.4.3 (Focus Order) — критерий успеха уровня A. Требует, чтобы порядок фокуса сохранял смысл контента при последовательной навигации. Нарушение считается критическим и блокирует сертификацию.
Скрытые элементы должны иметь isAccessibilityElement = false в iOS или visibility = gone/invisible в Android. При появлении — программно переместите фокус через UIAccessibility.post(notification: .layoutChanged).
iOS управляет через accessibilityElements и shouldGroupAccessibilityElement, Android — через атрибуты nextFocus* и AccessibilityNodeInfo. Принцип одинаков: геометрический порядок по умолчанию с возможностью переопределения.
Установите descendantFocusability = "beforeDescendants" на корневом элементе и настройте порядок в адаптере через onInitializeAccessibilityNodeInfo для каждой ячейки.
Подключите аппаратную клавиатуру через Bluetooth или USB. В iOS нажмите Tab для перемещения фокуса. В Android включите TalkBack и используйте клавиши Tab и стрелки.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также