Swipe: ما هو، معالجة التمرير في التطوير المحمول

المؤلف: IT Sectr نُشر: 2026-02-27 وقت القراءة: 8 دق
Swipe (التمرير) هو إيماءة متقطعة يقوم فيها المستخدم بتحريك إصبعه عبر الشاشة في اتجاه واحد — إلى اليمين أو اليسار أو الأعلى أو الأسفل. على عكس السحب (drag)، لا يتتبع التمرير الحركة المستمرة: يتم التعرف عليه كإجراء مكتمل بعد أن يتجاوز الإصبع حدًا أدنى ويرتفع عن الشاشة. وفقًا لدراسة تجربة المستخدم من Nielsen Norman Group (2024)، فإن التمرير هو ثاني أكثر الإيماءات شيوعًا في تطبيقات المحمول بعد النقر، حيث يُستخدم في 23% من تفاعلات المستخدم.

أهم النقاط

  • Swipe هي إيماءة متقطعة: يتم التعرف عليها كإجراء مكتمل بعد تجاوز الحد ورفع الإصبع.
  • في iOS، تتم معالجة التمرير عبر UISwipeGestureRecognizer مع تكوين الاتجاه وعدد الأصابع.
  • في Android، يُستخدم GestureDetector مع onFling(MotionEvent) أو OnSwipeTouchListener لواجهة برمجة تطبيقات مبسطة.
  • حد التمرير هو ~20px في iOS (viewConfiguration) ويتم تكوينه عبر VelocityTracker في Android.
  • للقوائم (UITableView/RecyclerView)، تُستخدم الآليات المدمجة — UISwipeActionsConfiguration و ItemTouchHelper — بدلاً من التعرف اليدوي.

ما هو التمرير؟

التمرير هو حركة انزلاق سريعة للإصبع عبر سطح الشاشة تنتهي بالرفع. يمكن أن يكون التمرير قصيرًا (قلب عنصر) أو طويلًا (فتح ستارة)، لكن السمة الرئيسية هي الانقطاع: يتعرف عليه النظام كحدث واحد، وليس كسلسلة متصلة من الإحداثيات.

تكتشف أنظمة التشغيل المحمولة التمرير بناءً على ثلاثة معايير: المسافة الدنيا (الحد)، والسرعة الدنيا (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:) أو منطقًا مخصصًا.

التمرير في iOS: UISwipeGestureRecognizer

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

في 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 وواجهات البطاقات — وهذا يعطي تحكمًا في حساسية التمرير دون التضحية بالأداء.

التمرير في القوائم: SwipeActions و ItemTouchHelper

للقوائم (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 التي تحتوي على إجراءات تمرير في القوائم.

أمثلة كود بلغة Swift و Kotlin

مثال 1: التمرير لليسار في iOS (Swift)

يضيف UISwipeGestureRecognizer إلى عرض للعودة إلى الشاشة السابقة. يظهر الكود الحد الأدنى من تكوين المُتعرف مع الاتجاه .left.

swift
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 متقطع ويستدعي الإجراء فقط عند التعرف الناجح.

مثال 2: التمرير في Android (Kotlin) مع OnSwipeTouchListener مخصص

ينفذ غلافًا بسيطًا لـ GestureDetector يحدد اتجاه التمرير ويستدعي ردود اتصال onSwipeLeft/Right.

kotlin
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() } }).

مثال 3: SwipeActions في iOS (UITableView)

يضيف زر حذف عند التمرير لليسار على خلية جدول. يستخدم API مدمج في iOS 11+ بدون تعرف يدوي على الإيماءات.

swift
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 و Android؟

منطق الإيماءة متطابق، لكن واجهة برمجة التطبيقات تختلف. يستخدم iOS UISwipeGestureRecognizer مع direction. يستخدم Android GestureDetector.onFling() بحدود مخصصة. النتيجة هي نفس تجربة المستخدم مع تنفيذ مختلف. تقوم الأطر عبر المنصات (Flutter، React Native) بتجريد الفرق من خلال واجهة برمجة تطبيقات تمرير موحدة.

كيف أتعامل مع التمرير في قائمة؟

في iOS (11+)، استخدم UISwipeActionsConfiguration في UITableViewDelegate. في Android، استخدم ItemTouchHelper.SimpleCallback مع RecyclerView. تحل واجهات برمجة التطبيقات هذه محل GestureRecognizers اليدوية، وتدعم أزرار الإجراءات والرسوم المتحركة، وتتعامل بشكل صحيح مع تعارضات التمرير وتتكيف مع إرشادات واجهة المستخدم الخاصة بالمنصة.

الخلاصة

  • التمرير هو إيماءة انزلاق سريع متقطعة، ثاني أكثر الإيماءات شيوعًا بعد النقر (23% من التفاعلات).
  • في iOS، يعالج UISwipeGestureRecognizer التمرير في أربعة اتجاهات بحد ثابت ~20px.
  • في Android، يُستخدم GestureDetector.onFling() بحدود سرعة ومسافة مخصصة.
  • للقوائم، استخدم UISwipeActionsConfiguration (iOS) و ItemTouchHelper (Android) بدلاً من المتعرفات اليدوية.
  • يختلف التمرير عن التحريك في الانقطاع والسرعة العالية — لا تخلط بين هذه الإيماءات عند التصميم.
  • يبسط OnSwipeTouchListener المخصص في Android واجهة برمجة تطبيقات GestureDetector بإخفاء حساب الاتجاه.
  • في IT Sectr، نستخدم التمرير للتنقل وحذف العناصر والإعداد الأولي — الإيماءة بديهية ولا تتطلب تعلمًا.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا