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 (تالي)، مفتاح مخصص.
إذا كان العنصر صغيرًا جدًا بالنسبة للتركيز (أقل من 44pt)، فقم بزيادة منطقة اللمس عبر TouchDelegate في iOS أو minWidth/minHeight في Android. وفقًا لـ Google Material Design, 2024، الحد الأدنى لمنطقة اللمس هو 48×48dp. يركز VoiceOver و TalkBack على المربع المحيط للعنصر. قد لا يمكن الوصول إلى العناصر الأصغر من 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، وليس حسب الهندسة
return shapes.sorted { $0.zIndex < $1.zIndex }
}
set {}
}
}
في Android، لعرض مخصص، تجاوز 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 تلقائيًا منطقة التركيز بناءً على إطار العنصر. إذا كان للعنصر تحويل (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()))
}
الطريقة الأكثر موثوقية هي اختبار سيناريو واجهة المستخدم: املأ النموذج خطوة بخطوة (البريد الإلكتروني → كلمة المرور → إرسال)، للتحقق من اكتمال كل خطوة بنجاح. إذا كان ترتيب التركيز مكسورًا، سيفشل السيناريو عند محاولة التفاعل مع عنصر خارج التركيز.
أداة Accessibility Inspector في Xcode تعرض شجرة إمكانية الوصول الكاملة. يمكنك التنقل عبر العناصر بترتيب 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. سوف نقدم لك النصح ونقترح أفضل حل.