Focus Order دنبالهای است که در آن عناصر رابط کاربری هنگام پیمایش با صفحهکلید، Switch Control، VoiceOver یا TalkBack فوکوس دریافت میکنند. در برنامههای موبایل، ترتیب فوکوس تعیین میکند که کاربر چگونه بین کنترلها با حرکات یا دکمهها جابهجا میشود. طبق W3C WCAG 2.2, Success Criterion 2.4.3, 2023، فوکوس باید در ترتیب منطقیای دنبال شود که معنای محتوا را حفظ کند. نقض این اصل یکی از دلایل رایج عدم پذیرش ممیزی دسترسپذیری است.
نکات کلیدی
Focus Order دنبالهای است که در آن کاربر با روشهای ورودی جایگزین بین عناصر تعاملی جابهجا میشود: صفحهکلید (Tab)، Switch Control (قدم به قدم)، VoiceOver (حرکت به راست/چپ) یا TalkBack. برخلاف ماوس یا صفحه لمسی که کاربر عنصر را مستقیماً انتخاب میکند، پیمایش فوکوس خطی است — هر قدم فوکوس را به عنصر بعدی میبرد.
طبق Apple HIG, 2024، VoiceOver از ترتیب عناصر در درخت دسترسپذیری استفاده میکند که بر اساس چیدمان بصری ساخته میشود: گوشه بالا سمت چپ → گوشه پایین سمت راست. اگر صفحه شامل چیدمان پیچیدهای باشد (ستونها، 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)، ناحیه لمس را از طریق TouchDelegate در iOS یا minWidth/minHeight در Android افزایش دهید. طبق Google Material Design, 2024، حداقل ناحیه لمس 48×48dp است. VoiceOver و TalkBack روی bounding box عنصر فوکوس میکنند. عناصر کوچکتر از 30pt ممکن است برای فوکوس حرکتی در دسترس نباشند — کاربر فیزیکی نمیتواند با انگشت به آنها برسد.
فوکوس پرشی — زمانی که پس از یک عمل (مثلاً حذف یک عنصر) فوکوس به ابتدای فهرست یا دکمه سیستمی «بازگشت» منتقل میشود. کاربر VoiceOver زمینه را از دست میدهد. راهحل: فوکوس را بهصورت برنامهنویسی به عنصری که نزدیک به عنصر حذفشده است منتقل کنید.
فوکوس نامرئی — عنصر فوکوس دریافت میکند، اما نشانگر بصری وجود ندارد (کاربران صفحهکلید نمیبینند کجا هستند). در iOS برای نشانگرهای سفارشی UIAccessibility.isVoiceOverRunning را بررسی کنید. طبق Deque University, 2024، فوکوس نامرئی دومین دلیل رایج شکست ممیزی دسترسپذیری است.
پنجرههای مدال — فوکوس پس از باز شدن پنجره مدال روی محتوای پسزمینه باقی میماند. در iOS، نمای مدال بهطور خودکار فوکوس را جذب میکند اگر modalPresentationStyle = .pageSheet تنظیم شده باشد. در Android از setFocusable(true) روی کانتینر دیالوگ استفاده کنید.
مشکل معکوس: فوکوس داخل پنجره مدال گیر میکند و نمیتواند خارج شود (بهجز با بستن). این فقط برای پنجرههای مدال مجاز است — کاربر باید آگاهانه پنجره را ببندد. برای صفحههای عادی تله فوکوس یک خطای بحرانی است. راهحل: مطمئن شوید که آخرین عنصر پنجره مدال (دکمه «بستن») فوکوس را به عقب برمیگرداند.
برای صفحههای سفارشی (نقشهها، بومها، بازیها) ترتیب هندسی خودکار قابل استفاده نیست. توسعهدهنده باید درخت دسترسپذیری را بهصورت دستی بسازد. در iOS برای این کار متد UIAccessibilityContainer بازنویسی میشود.
نمونه برای بوم سفارشی:
class CanvasView: UIView {
var shapes: [ShapeView] = []
override var accessibilityElements: [Any]? {
get {
// شکلها را بر اساس Z-index مرتب میکنیم، نه بر اساس هندسه
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 نمیتواند درخت دسترسپذیری را برای عناصر متحرک بسازد. 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 درخت کامل دسترسپذیری را نشان میدهد. میتوانید در ترتیب VoiceOver بین عناصر حرکت کنید و مسیر دقیق فوکوس را ببینید. از تب «Audit» برای جستجوی خودکار نقضهای Focus Order استفاده کنید.
سوالات متداول
WCAG 2.4.3 (Focus Order) — معیار موفقیت سطح A. الزام میکند که ترتیب فوکوس معنای محتوا را هنگام پیمایش متوالی حفظ کند. نقض بحرانی تلقی میشود و گواهینامه را مسدود میکند.
عناصر پنهان باید در iOS isAccessibilityElement = false یا در Android visibility = gone/invisible داشته باشند. هنگام ظاهر شدن، فوکوس را از طریق UIAccessibility.post(notification: .layoutChanged) بهصورت برنامهنویسی منتقل کنید.
iOS از طریق accessibilityElements و shouldGroupAccessibilityElement مدیریت میکند، Android — از طریق ویژگیهای nextFocus* و AccessibilityNodeInfo. اصل یکسان است: ترتیب هندسی بهصورت پیشفرض با امکان بازنویسی.
descendantFocusability = "beforeDescendants" را روی عنصر ریشه تنظیم کنید و ترتیب را در آداپتر از طریق onInitializeAccessibilityNodeInfo برای هر سلول پیکربندی کنید.
صفحهکلید سختافزاری را از طریق Bluetooth یا USB متصل کنید. در iOS برای جابهجایی فوکوس Tab را فشار دهید. در Android TalkBack را فعال کنید و از کلیدهای Tab و فلش استفاده کنید.
جمعبندی
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید