السحب والإفلات — إيماءة تفاعلية لسحب كائن مرئي عبر الشاشة بإصبعك. تتكون الإيماءة من ثلاث مراحل: الإمساك (الضغط المطول أو اللمس)، التحريك (تحريك الإصبع مع الكائن)، والإفلات (الإسقاط في المنطقة المستهدفة). يُستخدم لإعادة ترتيب العناصر، ونقل الملفات بين المجلدات، وتنظيم الواجهات (drag to reorder, drag to resize). في iOS يتم تنفيذه عبر UIDragInteraction، وفي Android — عبر DragEvent وstartDragAndDrop.
الرئيسية
السحب والإفلات (DnD) هو طريقة للتعامل مع العناصر الرسومية للواجهة حيث يمسك المستخدم بكائن بإصبعه أو الماوس، وينقله إلى موضع آخر، ويُفلته. في واجهات الجوال، يبدأ السحب والإفلات عادةً بالضغط المطول — يضغط المستخدم على العنصر لمدة 300–500 مللي ثانية، وبعد ذلك يتحول النظام إلى وضع السحب، وتحرك حركة الإصبع الكائن.
تاريخياً، ظهر السحب والإفلات في واجهات سطح المكتب (Xerox Star، 1981؛ Macintosh، 1984) كبديل لأوامر النسخ/اللصق. على المنصات المحمولة، ظهر الدعم الكامل في iOS 11 (2017) مع UIDragInteraction وفي Android 7.0 (2016) مع السحب والإفلات متعدد النوافذ. قبل ذلك، كان السحب في التطبيقات المحمولة يُنفذ بحلول مخصصة عبر مستقبلات الإيماءات.
المفاهيم الأساسية: جلسة السحب (drag session)، عناصر السحب (drag items)، معاينة السحب (drag preview — تمثيل مرئي للكائن)، منطقة الإسقاط (drop target)، اقتراح الإسقاط (drop proposal — الإجراء عند الإفلات: نقل، نسخ، ربط). يدير النظام الجلسة على مستوى نظام التشغيل، مما يسمح بسحب الكائنات بين التطبيقات المختلفة (مثلاً، صورة من المعرض إلى الملاحظات).
السحب والإفلات ينقسم إلى ثلاث مراحل متتالية. المرحلة 1 — الإمساك (بدء السحب): يبدأ المستخدم الإيماءة (عادةً الضغط المطول)، ينشئ النظام جلسة سحب، يلتقط بيانات الكائن ويعرض معاينة السحب — نسخة شبه شفافة من العنصر تتبع الإصبع. في هذه المرحلة، يرفض النظام الإيماءات الأخرى (التمرير، تحديد النص) حتى يكتمل السحب والإفلات.
المرحلة 2 — التحريك (حركة السحب): يتحرك الإصبع عبر الشاشة، تتبع معاينة السحب نقطة اللمس. يحدد النظام باستمرار منطقة الإسقاط تحت الإصبع ويرسل أحداث الدخول (drag-enter)، والحركة (drag-move)، والخروج (drag-exit). يمكن لمنطقة الإسقاط أن تتفاعل بصرياً: تبرز، تتوسع، أو تظهر خط إدراج. تستمر هذه المرحلة من 0.5 إلى 10+ ثوانٍ حسب تعقيد المهمة.
المرحلة 3 — الإفلات (الإسقاط): يرفع الإصبع عن الشاشة. ينهي النظام جلسة السحب ويستدعي رد النداء للعنصر المستهدف. إذا رفضت منطقة الإسقاط الإفلات (مثلاً، سحب ملف إلى مجلد بدون صلاحيات كتابة)، تعود معاينة السحب برسوم متحركة إلى الموضع الأصلي (إلغاء السحب). إذا قُبل الإفلات، يختفي العنصر من موضعه الأصلي ويظهر في الهدف.
| المرحلة | حدث iOS | حدث Android | الحالة المرئية |
|---|---|---|---|
| الإمساك | UIDragInteractionDelegate.dragSessionWillBegin | View.OnLongClickListener → startDragAndDrop | تظهر معاينة السحب تحت الإصبع |
| التحريك | UIDropInteractionDelegate.dragSessionDidUpdate | View.OnDragListener.onDrag (ACTION_DRAG_MOVED) | تتحرك المعاينة، تُبرز المنطقة المستهدفة |
| الإفلات | UIDropInteractionDelegate.performDrop | View.OnDragListener.onDrag (ACTION_DROP) | رسوم متحركة للإسقاط أو العودة |
السحب بين التطبيقات — ميزة متاحة على كلا المنصتين. يدعم iOS السحب والإفلات بين التطبيقات في وضعي Split View وSlide Over (iPad). يدعم Android السحب بين التطبيقات في وضع Split Screen. تُنقل البيانات عبر UIPasteConfiguration (iOS) أو ClipData (Android). يحدد المطور أنواع البيانات المدعومة (UTI لنظام iOS، MIME لنظام Android).
UIDragInteraction — واجهة برمجة التطبيقات الرئيسية في iOS للسحب، المُضافة في iOS 11. لتمكين السحب من طريقة العرض، أضف UIDragInteraction مع UIDragInteractionDelegate. يُحدد المفوض العناصر القابلة للسحب ومظهرها وسلوكها. لاستقبال العناصر، استخدم UIDropInteraction مع UIDropInteractionDelegate الذي يُحدد ما إذا كانت طريقة العرض يمكنها قبول الإفلات وكيفية معالجة البيانات.
// UIDragInteraction — سحب عنصر من مجموعة
class DragViewController: UIViewController, UIDragInteractionDelegate {
@IBOutlet var dragSourceView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let dragInteraction = UIDragInteraction(delegate: self)
dragSourceView.addInteraction(dragInteraction)
}
func dragInteraction(_ interaction: UIDragInteraction,
itemsForBeginning session: UIDragSession) -> [UIDragItem] {
guard let data = Data(contentsOf: selectedFileURL) else { return [] }
let itemProvider = NSItemProvider(object: data as NSData)
let dragItem = UIDragItem(itemProvider: itemProvider)
// معاينة السحب — نسخة مصغرة من العنصر
dragItem.previewProvider = {
let previewView = UIImageView(image: thumbnailImage)
previewView.alpha = 0.8
return UIDragPreview(view: previewView)
}
return [dragItem]
}
}UIDropInteractionDelegate — الواجهة المقابلة لاستقبال الإفلات. تتحقق طريقة canHandle(_:) مما إذا كان نوع البيانات مدعوماً. تُحدث sessionDidUpdate(_:) الحالة بصرياً (التمييز، خط الإدراج). تنفذ performDrop(_:) المعالجة النهائية للبيانات. يستدعي iOS تلقائياً UIDropProposal مع نوع العملية: .move (نقل)، .copy (نسخ)، أو .forbidden (محظور). يؤثر نوع العملية على العرض المرئي (أيقونة + في زاوية المعاينة للنسخ، علامة منع للمحظور).
View.startDragAndDrop() — طريقة Android SDK لبدء السحب، المتاحة منذ API 24 (Android 7.0). تُستدعى بعد الإمساك بالعنصر (عادةً في onLongClick). المعلمات: ClipData (البيانات)، DragShadowBuilder (الظل المرئي)، Object myLocalState (الحالة المحلية)، flags. ينشئ DragShadowBuilder نقطية bitmap تتبع الإصبع. يدير النظام تلقائياً الرسوم المتحركة وأحداث اللمس أثناء السحب.
// السحب والإفلات لعنصر RecyclerView مع إعادة الترتيب
class ReorderAdapter(private val items: MutableList<String>) :
RecyclerView.Adapter<ReorderAdapter.ViewHolder>() {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.setOnLongClickListener {
// إنشاء ClipData لنقل البيانات
val clipData = ClipData.newPlainText("item", items[position])
// DragShadowBuilder مخصص مع معاينة مصغرة
val shadowBuilder = object : View.DragShadowBuilder(holder.itemView) {
override fun onProvideShadowMetrics(shadowSize: Point, shadowTouchPoint: Point) {
shadowSize.set(itemView.width, itemView.height / 2)
}
}
holder.itemView.startDragAndDrop(clipData, shadowBuilder, position, 0)
true
}
}
}استقبال الإفلات في Android يُنفذ عبر View.setOnDragListener(). يتلقى المستمع أحداث ACTION_DRAG_STARTED وACTION_DRAG_ENTERED وACTION_DRAG_LOCATION وACTION_DRAG_EXITED وACTION_DROP وACTION_DRAG_ENDED. في حدث ACTION_DROP، تُستخرج البيانات من ClipData ويُنفذ الإجراء المستهدف. بالنسبة لـ RecyclerView مع إعادة الترتيب، يُوصى باستخدام ItemTouchHelper.SimpleCallback — آلية مدمجة تعالج تلقائياً الضغط المطول للإمساك، ورسوم التحريك، وطرق onMove/onSwiped المساعدة.
إعادة ترتيب القوائم (السحب لإعادة الترتيب) — سيناريو السحب والإفلات الأكثر شيوعاً: يسحب المستخدم عناصر القائمة لتغيير ترتيبها. أمثلة: إعادة ترتيب جهات الاتصال في المفضلة، فرز المهام في المتتبعات (Trello، Todoist)، إعادة ترتيب المسارات في قائمة التشغيل (Spotify، Apple Music). يُنفذ عبر ItemTouchHelper في Android أو UILongPressGestureRecognizer مع إعادة ترتيب الخلايا في iOS.
مديري الملفات — السحب والإفلات لنسخ ونقل الملفات بين المجلدات. تطبيق ملفات iOS، وملفات Android من Google، وDropbox — جميعها تدعم السحب والإفلات. سحب ملف إلى مجلد ينقله داخله (نقل)، السحب مع الضغط على Option/Ctrl ينسخه (نسخ). تُبرز منطقة الإسقاط بصرياً عند التمرير، مُظهرة حدود المجلد.
لوحات كانبان — Trello وJira وNotion تستخدم السحب والإفلات لنقل البطاقات بين الأعمدة (المهام → قيد التنفيذ → تم). عند السحب، تنفصل البطاقة عن عمودها المصدر، وتتبع الإصبع، وعند عبور حدود عمود آخر تُدرج برسوم متحركة. يدعم iOS 11+ UIDragPreview مع تأثير التزيح (parallax)، حيث تميل المعاينة قليلاً عن نقطة اللمس، مما يخلق عمقاً.
أقسام Springboard (iOS) — سحب أيقونات التطبيقات على الشاشة الرئيسية لنظام iOS. الضغط المطول على أيقونة ينشط وضع «الاهتزاز» (تتذبذب الأيقونات)، وبعد ذلك يمكن للمستخدم نقل الأيقونات بين الصفحات وإنشاء مجلدات. هذا أحد أكثر أنماط تجربة المستخدم تعقيداً في السحب والإفلات، حيث يتطلب معالجة متزامنة للسحب ورسوم الاهتزاز والتجميع في مجلدات.
الملاحظات المرئية — ضرورية جداً للسحب والإفلات. يجب أن يتغير الكائن المسحوب: يتقلص (iOS: مقياس 0.8–0.9)، يصبح شبه شفاف (alpha 0.7–0.8)، ويرتفع فوق الواجهة بظل (shadow offset). يجب أن تتفاعل منطقة الإسقاط بصرياً عند المرور: تُبرز أو تتوسع أو تظهر حداً. بدون هذه الإشارات، لا يفهم المستخدم أين يمكنه إفلات الكائن.
رسوم العودة المتحركة (إلغاء السحب) — إذا حرر المستخدم العنصر خارج منطقة الإفلات أو فوق منطقة غير صالحة، يجب أن يعود الكائن برسوم متحركة إلى موضعه الأصلي. يوفر UIDragInteractionDelegate في iOS dragInteraction(_:sessionDidMove:) للتتبع وdragInteraction(_:session:willEndWith:) لرسوم العودة. يستقبل View.setOnDragListener في Android ACTION_DRAG_ENDED، حيث يمكن بدء رسوم العودة عبر ValueAnimator أو ObjectAnimator.
| التوصية | iOS | Android |
|---|---|---|
| الإمساك بالإيماءة | الضغط المطول (افتراضي) | OnLongClickListener |
| معاينة السحب | UIDragItem.previewProvider | DragShadowBuilder |
| الملاحظات اللمسية | UIImpactFeedbackGenerator | HapticFeedbackConstants.LONG_PRESS |
| رسوم العودة | UIDragPreviewTarget | ViewPropertyAnimator |
| اقتراح الإفلات | UIDropProposal (.move/.copy) | ACTION_DROP + flags |
| السحب بين التطبيقات | iOS 11+ Split View (iPad) | Android 7.0+ Split Screen |
إمكانية الوصول: قد لا يتمكن المستخدمون ذوو الإعاقات الحركية من أداء إيماءة السحب. قدم بدائل عبر أزرار «نقل لأعلى/لأسفل» للقوائم وقائمة سياقية «نقل إلى مجلد» للملفات. يدعم iOS VoiceOver إجراء «السحب» في العجلة الدوارة لنقل العناصر. يوفر Android TalkBack إجراءات مخصصة عبر AccessibilityNodeInfo.addAction(). يستخدم Nike Run Club وApple Health السحب والإفلات لإعادة ترتيب المقاييس على لوحة المعلومات — مع الوصول عبر الأزرار القياسية.
الأسئلة الشائعة
الإيماءة القياسية لبدء السحب والإفلات هي الضغط المطول لمدة 300–500 مللي ثانية. يستخدم iOS الآلية المدمجة UIDragInteraction التي تحول اللمس المستمر تلقائياً إلى إمساك. يتطلب Android استدعاءً صريحاً لـ startDragAndDrop() في رد النداء OnLongClickListener. في بعض الحالات (مثل الألعاب)، يمكن أن يبدأ السحب والإفلات بلمسة بسيطة دون تأخير.
نعم، السحب والإفلات بين التطبيقات مدعوم على iOS 11+ (iPad) وAndroid 7.0+. على iPad، يعمل السحب في وضعي Split View وSlide Over — يمكن للمستخدم أخذ نص من Safari وإفلاته في الملاحظات. في Android، يعمل السحب والإفلات بين التطبيقات في وضع Split Screen. تُنقل البيانات عبر UIPasteConfiguration (iOS) أو ClipData (Android) مع تحديد الأنواع المدعومة (UTI، MIME).
يُحل تعارض السحب والإفلات والتمرير من خلال تأخير الإمساك. إذا بدأ المستخدم تحريك إصبعه قبل انتهاء مهلة الضغط المطول (500 مللي ثانية)، تُفسر الإيماءة على أنها تمرير وليس سحباً وإفلاتاً. في iOS، يدير UIDragInteraction هذا التعارض تلقائياً عبر cancelsTouchesInView. في Android، يستخدم ItemTouchHelper عتبة — إذا تجاوزت الحركة حد TouchSlop، يُلغى الضغط المطول لصالح التمرير.
السحب والإفلات يدعم أي نوع بيانات: نص، صور، ملفات، URLs، كائنات مخصصة. يستخدم iOS NSItemProvider مع أنواع UTI (public.text، public.image، public.file-url). يستخدم Android ClipData مع أنواع MIME (text/plain، image/png، application/octet-stream). للبيانات المخصصة، يُنشأ ClipData.Item مع Intent. للسحب بين التطبيقات، تُحزم البيانات في تنسيقات عالمية: نص عادي، HTML، RTF، UIImage/PNG.
في iOS، تُعالج رسوم العودة عبر UIDragPreviewTarget — الموضع النهائي الذي تعود إليه المعاينة برسوم متحركة. في Android، استخدم ViewPropertyAnimator مع translateX/translateY لإعادة العنصر إلى موضعه الأصلي. مدة الرسوم المتحركة 200–300 مللي ثانية مع منحنى تخفيف (EaseOut). يضيف UIKit تلقائياً UIDragPreview مع تأثير التزيح الذي يعود بسلاسة عند إلغاء الإيماءة.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.