أهم النقاط
التمرير هو حركة انزلاق سريعة للإصبع عبر سطح الشاشة تنتهي بالرفع. يمكن أن يكون التمرير قصيرًا (قلب عنصر) أو طويلًا (فتح ستارة)، لكن السمة الرئيسية هي الانقطاع: يتعرف عليه النظام كحدث واحد، وليس كسلسلة متصلة من الإحداثيات.
تكتشف أنظمة التشغيل المحمولة التمرير بناءً على ثلاثة معايير: المسافة الدنيا (الحد)، والسرعة الدنيا (velocity)، والاتجاه. إذا تحرك الإصبع ببطء أو كانت المسافة غير كافية، يفسر النظام الإيماءة على أنها تحريك (pan) أو نقر. تختلف قيم الحد بين المنصات: يستخدم iOS ثابتًا في UISwipeGestureRecognizer بحد ثابت، ويستخدم Android ViewConfiguration.getScaledTouchSlop() الذي يعيد ~8–16 dp حسب كثافة الشاشة.
وفقًا لـ وثائق مطوري Android، يتم تشغيل onFling (التمرير) في GestureDetector عند سرعات تزيد عن 100 px/s ومسافات من 50px. في IT Sectr، نستخدم التمرير للتنقل (المعارض، واجهات البطاقات، الإعداد الأولي) — هذه الإيماءة بديهية للمستخدمين من جميع الأعمار.
التمرير والتحريك (pan/drag) هما نوعان مختلفان من الإيماءات، على الرغم من أن كليهما يتضمن تحريك الإصبع عبر الشاشة. التمرير متقطع ويعني "تنفيذ إجراء واحد"، بينما التحريك مستمر ويعني "تحريك كائن ما دام الإصبع على الشاشة".
| المعامل | التمرير (Swipe) | التحريك (Pan/Drag) |
|---|---|---|
| النوع | متقطع | مستمر |
| الأحداث | واحد (مُعترف به/غير مُعترف به) | متعددة (.began → .changed → .ended) |
| السرعة | عالية (حركة سريعة) | أي (حركة بطيئة) |
| مثال iOS | UISwipeGestureRecognizer | UIPanGestureRecognizer |
| مثال Android | GestureDetector.onFling() | GestureDetector.onScroll() |
| السيناريو | حذف بريد إلكتروني، قلب صفحة | تحريك خريطة، سحب ملف |
| تتبع الإحداثيات | لا (الاتجاه فقط) | نعم (تحديث مستمر للإحداثيات) |
الاختيار بين التمرير والتحريك يعتمد على المهمة. للإجراءات على عنصر (حذف، أرشفة، فتح قائمة) استخدم التمرير. للتلاعب المباشر (التحريك، التكبير) استخدم التحريك. يتطلب خلط الإيماءات على نفس العنصر require(toFail:) أو منطقًا مخصصًا.
UISwipeGestureRecognizer هو فئة فرعية من UIGestureRecognizer تتعرف على التمرير المتقطع في أحد الاتجاهات الأربعة. على عكس UIPanGestureRecognizer، لا يولد أحداث .changed وسيطة: عند التعرف، يتم استدعاء الإجراء مرة واحدة بالضبط.
تحدد الخاصية direction (UISwipeGestureRecognizer.Direction) الاتجاه المتتبع — .left, .right, .up, .down. يمكن دمج الاتجاهات باستخدام قناع: [.left, .right] للتمرير الأفقي. تحدد الخاصية numberOfTouchesRequired عدد الأصابع — 1 افتراضيًا. ليس لدى UISwipeGestureRecognizer حد مسافة قابل للتكوين — يستخدم iOS قيمة مدمجة تبلغ حوالي 20px، كافية للتعرف الموثوق.
نظرًا لأن UISwipeGestureRecognizer هو مُتعرف متقطع، فإنه لا يتعارض مع UIPanGestureRecognizer: الحركة البطيئة تشغل pan، والحركة السريعة تشغل swipe. ومع ذلك، عند التعايش مع UITapGestureRecognizer على نفس العرض، قد يكون require(toFail:) ضروريًا لمنع التعرف الخاطئ على النقر.
في Android، واجهة برمجة التطبيقات الرئيسية للتعرف على التمرير هي GestureDetector مع رد اتصال OnGestureListener، وتحديدًا طريقة onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY). يحلل GestureDetector الموضع الأولي والنهائي للإصبع، وسرعة الحركة على المحاور، والمسافة، ثم يستدعي onFling عند تجاوز الحدود.
يتم تكوين الحدود الدنيا عبر ViewConfiguration: VelocityTracker.getMinVelocity() (~100 px/s) و ViewConfiguration.getScaledTouchSlop() (~8–16 dp). يُعد OnSwipeTouchListener المخصص نمطًا شائعًا يبسط واجهة برمجة التطبيقات: لا يحتاج المطور إلى حساب الاتجاه والسرعة يدويًا. لا تحتوي Android Support Library على "SwipeDetector" مدمج، لذا فإن الغلاف المخصص هو الحل القياسي.
للتمرير في القوائم، توصي Google باستخدام ItemTouchHelper.SimpleCallback من RecyclerView، الذي يجرد اكتشاف الإيماءة والرسوم المتحركة وردود اتصال onSwiped(). في IT Sectr، نستخدم SwipeListener مخصصًا لـ ViewPager2 وواجهات البطاقات — وهذا يعطي تحكمًا في حساسية التمرير دون التضحية بالأداء.
للقوائم (UITableView، RecyclerView)، توفر Apple و Google واجهات برمجة تطبيقات متخصصة تحل محل الإضافة اليدوية لـ UISwipeGestureRecognizer. تدعم واجهات برمجة التطبيقات هذه أزرار الإجراءات والرسوم المتحركة المخصصة وإيماءات "التمرير للحذف" — وهي أنماط قياسية في تطبيقات المحمول.
في iOS (11+)، يُستخدم UISwipeActionsConfiguration: الطريقة tableView(_:leadingSwipeActionsConfigurationForRowAt:) تعيد مصفوفة من UIContextualAction — أزرار بعنوان ونمط (destructive، normal) ومعالج. على سبيل المثال، "حذف" — .destructive بخلفية حمراء، "أرشفة" — .normal بخلفية رمادية. يقوم النظام بتحريك ظهور الأزرار واستدعاء كتلة completion بعد النقر.
في Android، يتم إرفاق ItemTouchHelper.SimpleCallback بـ RecyclerView عبر ItemTouchHelper.attachToRecyclerView(). تتضمن المعلمات أعلام الاتجاه (start، end، up، down) ورد اتصال onSwiped(viewHolder, direction). داخل onSwiped، يتم استدعاء adapter.notifyItemRemoved(position) لإزالة العنصر مع الرسوم المتحركة. يعالج ItemTouchHelper تلقائيًا الإيماءات وتعارضات التمرير وإرجاع العنصر إذا لم يكتمل التمرير. وفقًا لـ Google I/O 2024، يُستخدم ItemTouchHelper في 70% من تطبيقات Android التي تحتوي على إجراءات تمرير في القوائم.
يضيف UISwipeGestureRecognizer إلى عرض للعودة إلى الشاشة السابقة. يظهر الكود الحد الأدنى من تكوين المُتعرف مع الاتجاه .left.
import UIKit
class CardViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swipeLeft = UISwipeGestureRecognizer(
target: self,
action: #selector(handleSwipeLeft(_:))
)
swipeLeft.direction = .left
swipeLeft.numberOfTouchesRequired = 1
view.addGestureRecognizer(swipeLeft)
}
@objc private func handleSwipeLeft(_: UISwipeGestureRecognizer) {
guard let nav = navigationController else { return }
if nav.viewControllers.count > 1 {
nav.popViewController(animated: true)
} else {
nav.dismiss(animated: true)
}
}
}
يتم إرفاق المُتعرف بعرض الجذر لوحدة التحكم. يضمن فحص navigationController.viewControllers.count أن pop يتم تنفيذه فقط عند وجود شاشة سابقة في المكدس. لوحدات التحكم المشروطة، يُستخدم dismiss. لا يتطلب الكود فحص الحالة — UISwipeGestureRecognizer متقطع ويستدعي الإجراء فقط عند التعرف الناجح.
ينفذ غلافًا بسيطًا لـ GestureDetector يحدد اتجاه التمرير ويستدعي ردود اتصال onSwipeLeft/Right.
import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
open class OnSwipeTouchListener(context: Context) : View.OnTouchListener {
private val gestureDetector = GestureDetector(context, GestureListener())
override fun onTouch(v: View, event: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(event)
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
companion object {
private const val SWIPE_THRESHOLD = 100
private const val SWIPE_VELOCITY_THRESHOLD = 100
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val diffX = e2.x - e1!!.x
val diffY = e2.y - e1.y
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD
) {
if (diffX > 0) onSwipeRight()
else onSwipeLeft()
return true
}
} else {
if (Math.abs(diffY) > SWIPE_THRESHOLD
&& Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD
) {
if (diffY > 0) onSwipeDown()
else onSwipeUp()
return true
}
}
return false
}
}
open fun onSwipeLeft() {}
open fun onSwipeRight() {}
open fun onSwipeUp() {}
open fun onSwipeDown() {}
}
يمدد OnSwipeTouchListener SimpleOnGestureListener، منفذًا onFling. يحدد diffX/diffY الاتجاه، SWIPE_THRESHOLD (100px) و SWIPE_VELOCITY_THRESHOLD (100 px/s) هما الحدان. الاستخدام: view.setOnTouchListener(OnSwipeTouchListener(context).apply { onSwipeLeft = { dismiss() } }).
يضيف زر حذف عند التمرير لليسار على خلية جدول. يستخدم API مدمج في iOS 11+ بدون تعرف يدوي على الإيماءات.
func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(
style: .destructive,
title: "حذف"
) { _, _, completionHandler in
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
completionHandler(true)
}
deleteAction.backgroundColor = .systemRed
let config = UISwipeActionsConfiguration(actions: [deleteAction])
config.performsFirstActionWithFullSwipe = true
return config
}
تعيد الطريقة UISwipeActionsConfiguration مع مصفوفة من UIContextualAction. style: .destructive يضبط الخلفية الحمراء تلقائيًا. performsFirstActionWithFullSwipe = true يسمح بتنفيذ الإجراء بتمريرة كاملة دون نقر إضافي. في IT Sectr، نستخدم واجهة برمجة التطبيقات هذه لجميع القوائم ذات الإجراءات — وهي موحدة، وتدعم VoiceOver، ولا تتطلب GestureRecognizers مخصصة.
الأسئلة الشائعة
التمرير هو إيماءة متقطعة بحركة سريعة ورفع الإصبع، والتحريك هو سحب مستمر مع تتبع الإحداثيات. يتم تشغيل UISwipeGestureRecognizer مرة واحدة، ويولد UIPanGestureRecognizer .began → .changed → .ended. يعتمد الاختيار على السيناريو: التمرير للإجراءات (حذف، قلب)، والتحريك للحركة (خريطة، منزلق).
منطق الإيماءة متطابق، لكن واجهة برمجة التطبيقات تختلف. يستخدم iOS UISwipeGestureRecognizer مع direction. يستخدم Android GestureDetector.onFling() بحدود مخصصة. النتيجة هي نفس تجربة المستخدم مع تنفيذ مختلف. تقوم الأطر عبر المنصات (Flutter، React Native) بتجريد الفرق من خلال واجهة برمجة تطبيقات تمرير موحدة.
في iOS (11+)، استخدم UISwipeActionsConfiguration في UITableViewDelegate. في Android، استخدم ItemTouchHelper.SimpleCallback مع RecyclerView. تحل واجهات برمجة التطبيقات هذه محل GestureRecognizers اليدوية، وتدعم أزرار الإجراءات والرسوم المتحركة، وتتعامل بشكل صحيح مع تعارضات التمرير وتتكيف مع إرشادات واجهة المستخدم الخاصة بالمنصة.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا