اسحب وأفلت في تطبيقات iOS



تُعد آلية Drag & Drop تعمل على iOS 11 و iOS 12 طريقة لنسخ البيانات أو نقلها بشكل غير متزامن داخل تطبيق واحد وبين التطبيقات المختلفة. على الرغم من أن هذه التكنولوجيا عمرها 30 عامًا تقريبًا ، فقد أصبحت حرفيا تقنية "اختراق" على iOS نظرًا لحقيقة أنه عند سحب شيء ما على iOS ، يتيح لك multitouch التفاعل بحرية مع بقية النظام وجمع البيانات لإعادة التعيين من تطبيقات مختلفة.

يجعل iOS من الممكن التقاط عناصر متعددة في وقت واحد. علاوة على ذلك ، لا يجب أن تكون في سهولة الوصول للاختيار: يمكنك أخذ الكائن الأول ، ثم الانتقال إلى تطبيق آخر والحصول على شيء آخر - سيتم جمع جميع الأشياء في "كومة" تحت إصبعك. ثم اتصل بالرصيف العالمي على الشاشة ، افتح أي تطبيق هناك والتقط الكائن الثالث ، ثم انتقل إلى الشاشة مع تشغيل التطبيقات ، ودون تحرير الكائنات ، قم بتفريغها في أحد البرامج المفتوحة. هذه الحرية في العمل ممكنة على iPad ، على iPhone ، وتقتصر تغطية Drag & Drop في iOS على إطار تطبيق واحد.

تحتوي التطبيقات الأكثر شيوعًا ( IbisPaint X و Chrome و IbisPaint X و Mail و Photos و Files وما إلى ذلك) على آلية Drag & Drop . بالإضافة إلى ذلك ، زودت Apple المطورين API بسيطة للغاية وبديهية لتضمين آلية Drag & Drop في تطبيقك. تعمل آلية Drag & Drop ، تمامًا مثل الإيماءات ، على UIView وتستخدم مفهوم التفاعلات ، مثل الإيماءات قليلاً ، لذلك يمكنك التفكير في آلية Drag & Drop ببساطة كإيماءة قوية حقًا.

من السهل جدًا الاندماج في تطبيقك ، بالإضافة إلى الإيماءات. خاصة إذا كان تطبيقك يستخدم جدول UITableView أو مجموعة UICollectionView ، لأنه بالنسبة لهم API Drag & Drop تحسين API Drag & Drop ورفعه إلى مستوى أعلى من التجريد بمعنى أن Collection View نفسها تساعدك في فهرسة مسار عنصر المجموعة الذي تريد سحبه وإفلاته Drag . إنها تعرف مكان إصبعك وتفسره على أنه indexPath لعنصر المجموعة الذي تقوم "بسحبه" Drag في الوقت الحالي ، أو باعتباره indexPath لعنصر المجموعة حيث "تسقط" Drop شيء ما. لذا فإن Collection View تمدك بـ indexPath ، ولكن بخلاف ذلك ، فهي تمامًا نفس API Drag & Drop UIView العادية.

تتكون عملية Drag & Drop على iOS 4 مراحل مختلفة:

ارفع


الرفع (الرفع) - يحدث هذا عندما يقوم المستخدم بإشارة ضغط طويلة ، مشيرًا إلى العنصر الذي سيكون "سحب وإسقاط". في هذه اللحظة ، يتم تشكيل ما يسمى بـ " lift preview " خفيفة الوزن للغاية للعنصر المشار إليه ، ثم يبدأ المستخدم في سحب أصابعه.



اسحب (اسحب واسقط)


السحب (السحب والإفلات) - هذا عندما يقوم المستخدم بتحريك الكائن على سطح الشاشة. خلال هذه المرحلة ، يمكن تعديل " lift preview " لهذا الكائن (تظهر علامة "+" خضراء أو علامة أخرى) ...



... يُسمح أيضًا ببعض التفاعل مع النظام: يمكنك النقر فوق كائن آخر وإضافته إلى جلسة "السحب والإفلات" الحالية:



إسقاط


يحدث الانخفاض عندما يرفع المستخدم إصبعه. في هذه اللحظة ، يمكن أن يحدث شيئان: إما أن يتم تدمير كائن Drag ، أو سيتم "إسقاط" كائن Drop في الوجهة.



نقل البيانات


إذا لم يتم إلغاء عملية السحب والإفلات وحدثت إعادة تعيين " الإفلات " ، فسيحدث نقل البيانات (نقل البيانات) ، حيث تطلب "نقطة الإفلات" البيانات من "المصدر" ، ويحدث نقل البيانات غير المتزامن.

في هذا البرنامج التعليمي ، سنوضح لك كيفية دمج آلية Drag & Drop بسهولة في تطبيق iOS الخاص بك باستخدام تطبيق العرض التجريبي للصور ، المأخوذ من دورة الواجبات المنزلية CS193P في ستانفورد .
سنمنح Collection View القدرة على ملء أنفسنا بالصور في الخارج ، بالإضافة إلى إعادة تنظيم عناصر INSIDE باستخدام آلية Drag & Drop . بالإضافة إلى ذلك ، سيتم استخدام هذه الآلية لإلقاء العناصر غير الضرورية في " Collection View في "سلة المهملات" ، وهي UIView عادية ويتم تمثيلها بواسطة زر في لوحة التنقل. يمكننا أيضًا مشاركة الصور التي تم جمعها في معرضنا مع تطبيقات أخرى باستخدام آلية Drag & Drop ، على سبيل المثال ، Notes أو Notes أو Mail أو مكتبة صور ( Photo ).

ولكن قبل التركيز على تنفيذ آلية Drag & Drop في تطبيق العرض التوضيحي "معرض الصور" ، سوف أتناول مكوناتها الرئيسية بإيجاز شديد.

ميزات التطبيق التجريبي "معرض الصور"


واجهة المستخدم ( UI ) لتطبيق معرض الصور بسيطة للغاية. هذا هو "مقتطف شاشة" من Image Gallery Collection View Controller المدرجة في Navigation Controller :



الجزء المركزي من التطبيق هو بالتأكيد Image Gallery Collection View Controller ، والتي تدعمها فئة ImageGalleryCollectionViewController مع نموذج معرض الصور كمتغير var imageGallery = ImageGallery () :



يتم تمثيل النموذج من خلال بنية ImageGallery الهيكلية التي تحتوي على مجموعة من صور الصور ، حيث يتم وصف كل صورة ببنية هيكل ImageModel التي تحتوي على URL لموقع الصورة (لن نقوم بتخزين الصورة نفسها) ونسبة العرض إلى الارتفاع:



يطبق ImageGalleryCollectionViewController بروتوكول DataSource :



تحتوي الخلية المخصصة في مجموعة الخلايا على صورة imageView: UIImageView! ومؤشر النشاط الدوار: UIActivityIndicatorView! وهي مدعومة من subclass ImageCollectionViewCell من فئة UICollectionViewCell :



Public API لفئة ImageCollectionViewCell هي عنوان URL لصورة imageURL . بمجرد تثبيته ، UI تحديث UI بنا ، أي يتم تحديد بيانات الصورة بشكل غير متزامن على هذه الصورة URL وعرضها في الخلية. أثناء استرداد البيانات من الشبكة ، يعمل مؤشر نشاط الدوار ، مما يشير إلى أننا في عملية استرداد البيانات.

أستخدم قائمة الانتظار العالمية (qos: .userInitiated) مع وسيطة جودة خدمة qos للحصول على البيانات في URL معين ، والذي تم تعيينه على .userInitiated لأنني حدد البيانات بناءً على طلب المستخدم:



في كل مرة تستخدم فيها المتغيرات الخاصة بك داخل إغلاق ، في حالتنا هي imageView و imageURL ، فإن المترجم يجبرك على وضع نفسك أمامها . بحيث تسأل نفسك: "هل هناك" وصلة دورية للذاكرة "؟" ليس لدينا " memory cycle " صريحة هنا ، لأن الذات نفسها ليس لديها مؤشر لهذا الإغلاق.

ومع ذلك ، في حالة تعدد مؤشرات الترابط ، يجب أن تضع في اعتبارك أن الخلايا في Collection View قابلة لإعادة الاستخدام بفضل طريقة dequeueReusableCell . في كل مرة تظهر فيها خلية (جديدة أو معاد استخدامها) على الشاشة ، يتم تنزيل الصورة من الشبكة بشكل غير متزامن (في هذا الوقت ، يدور " الدوار " لمؤشر نشاط الدوار ).

بمجرد اكتمال التنزيل واستلام الصورة ، يتم تحديث UI لخلية المجموعة هذه. لكننا لا ننتظر تحميل الصورة ، نواصل التمرير عبر المجموعة وخلافة المجموعة التي قمنا بوضع علامة عليها تغادر الشاشة دون تحديث UI بنا. ومع ذلك ، يجب أن تظهر صورة جديدة أدناه وسيتم إعادة استخدام نفس الخلية التي غادرت الشاشة ، ولكن لصورة أخرى ، والتي قد يتم تحميلها وتحديثها بسرعة. في هذا الوقت ، سيعود تحميل الصورة الذي بدأ سابقًا في هذه الخلية وسيتم تحديث الشاشة ، مما سيؤدي إلى نتيجة غير صحيحة. هذا لأننا ندير أشياء مختلفة تعمل مع الشبكة في مواضيع مختلفة. يعودون في أوقات مختلفة.

كيف نصلح الوضع؟
في إطار آلية GCD التي نستخدمها ، لا يمكننا إلغاء تنزيل صورة الخلية التي غادرت الشاشة ، ولكن يمكننا ، عند وصول بيانات imageData من الشبكة ، التحقق من URL URL الذي تسبب في تحميل هذه البيانات ومقارنتها مع تلك التي يريدها المستخدم في هذه الخلية في الوقت الحالي ، أي imageURL . إذا لم تتطابق ، فلن نقوم بتحديث خلية UI وننتظر بيانات الصورة التي نحتاجها:



هذا السطر الذي يبدو سخيفًا من عنوان url للكود == self.imageURL يجعل كل شيء يعمل بشكل صحيح في بيئة متعددة الخيوط تتطلب خيالًا غير قياسي. والحقيقة هي أن بعض الأشياء في البرمجة متعددة الخيوط تحدث بترتيب مختلف عن كتابة الشفرة.

إذا لم يكن من الممكن تحديد بيانات الصورة ، يتم إنشاء صورة برسالة خطأ في شكل سلسلة "خطأ" ورمز تعبيري مع "عبوس". فقط المساحة الفارغة في Collection View قد تربك المستخدم قليلاً:



لا نريد للصورة التي تحتوي على رسالة الخطأ أن تكرر الجانب النسبي لصورة الخطأ هذه ، لأنه في هذه الحالة سيتم تمديد النص مع الرمز التعبيري أو ضغطه. نود أن يكون مربعًا محايدًا ، أي أنه سيكون له نسبة عرض إلى ارتفاع قريبة من 1.0.



يجب أن نبلغ مراقبنا بهذه الرغبة حتى يصحح نسبة العرض إلى الارتفاع بالنسبة للفهرس المقابل في صورته للمعرض النموذجي. هذه مهمة مثيرة للاهتمام ، وهناك العديد من الطرق لحلها ، وسنختار أسهلها - باستخدام إغلاق اختياري var closAspectRatio: (() -> Void)؟ . يمكن أن يساوي صفرًا ولا يلزم تثبيته إذا لم يكن ذلك ضروريًا:



عند استدعاء الإغلاق changeAspectRatio؟ () في حالة جلب بيانات خاطئة ، أستخدم السلسلة الاختيارية . الآن يمكن لأي شخص مهتم في نوع ما من الإعدادات عند تلقي صورة خاطئة تعيين هذا الإغلاق على شيء محدد. وهذا بالضبط ما نقوم به في Controller بنا في طريقة cellForItemAt :



يمكن العثور على التفاصيل هنا .

لعرض الصور ذات الجانب الصحيح ، يتم استخدام طريقة sizeForItemAt لمفوض UICollectionViewDelegateFlowLayout :



بالإضافة إلى Collection View صور Collection View ، في UI لدينا UI قمنا بوضع Bar Button في لوحة التنقل مع صورة GarbageView مخصصة تحتوي على "سلة المهملات" كعرض فرعي :



في هذا الشكل ، يتم تغيير ألوان الخلفية لـ GarbageView نفسه وزر UIButton مع صورة "علبة القمامة" (توجد بالفعل خلفية شفافة) بحيث ترى أن المستخدم الذي "يفرغ" صور المعرض في "سلة المهملات" مساحة أكبر للمناورة عند إسقاط " إسقاط " من مجرد رمز سلة المهملات.
تحتوي فئة GarbageView على مُهيئان ويستخدمان أسلوب الإعداد () :



في طريقة الإعداد () ، أقوم أيضًا بإضافة myButton كطريقة عرض فرعية مع صورة "علبة القمامة" المأخوذة من Bar Button القياسي Bar Button :



قمت بتعيين خلفية شفافة لـ GarbageView :



سيتم تحديد حجم سلة المهملات وموقعها في طريقة layoutSubviews () لفئة UIView ، اعتمادًا على حدود UIView المحددة:



هذا هو الإصدار الأولي من التطبيق التجريبي "معرض الصور" ، وهو موجود على Github في المجلد ImageGallery_beginning . إذا قمت بتشغيل هذا الإصدار من تطبيق "معرض الصور" ، فسترى نتيجة التطبيق الذي يعمل على بيانات الاختبار ، والتي سنحذفها لاحقًا وسنملأ "معرض الصور" حصريًا خارج:



خطة تنفيذ آلية Drag & Drop في تطبيقنا هي كما يلي:

  1. أولاً ، سنمنح Collection View بنا القدرة على "السحب" Drag من الصور UIImage الخاصة به خارجياً ومحلياً ،
  2. ثم سنقوم بتعليم مجموعتنا من صور Collection View لقبول Drag "المسحوب" خارجيًا أو محليًا من UIImage ،
  3. سنقوم أيضًا بتعليم GarbageView الخاص بنا باستخدام زر سلة المهملات لقبول صور UIImage التي تم سحبها من Collection View المحلي وإزالتها من Collection View


إذا ذهبت إلى نهاية هذا البرنامج التعليمي وأكملت جميع التغييرات الضرورية في التعليمات البرمجية ، فستتلقى الإصدار النهائي من التطبيق التجريبي "معرض الصور" ، والذي تم فيه تنفيذ آلية Drag & Drop . وهو موجود على Github في المجلد ImageGallery_finished .

يتم توفير أداء آلية Drag & Drop في Collection View خلال اثنين من المندوبين الجدد.
يتم تكوين أساليب المندوب الأول ، سحب السحب ، لتهيئة وتخصيص السحب والإفلات.
طرق المفوض الثاني ، dropDelegate ، تكمل السحب والإفلات من Drags ، وتوفر ، في الأساس ، Data transfer البيانات وإعدادات الرسوم المتحركة المخصصة عند إعادة تعيين Drop ، بالإضافة إلى أشياء أخرى مماثلة.

من المهم ملاحظة أن كلا هذين البروتوكولين مستقلان تمامًا. يمكنك استخدام بروتوكول أو بروتوكول آخر إذا كنت بحاجة فقط إلى "سحب" Drag أو "إسقاط" Drop ، ولكن يمكنك استخدام كلا البروتوكولين في وقت واحد Drag وإفلات Drag والإسقاط في نفس الوقت ، مما يفتح وظائف إضافية آلية Drag & Drop لإعادة ترتيب العناصر في Collection View .

اسحب Drag عناصر Drag من Collection View


يعد تنفيذ بروتوكول Drag أمرًا بسيطًا للغاية ، وأول شيء يجب عليك فعله دائمًا هو تعيين نفسك ، نفسك ، كمفوض السحب :



وبالطبع ، في أعلى فئة ImageGalleryCollectionViewController ، يجب أن تقول "نعم" ، ننفذ بروتوكول UICollectionViewDragDelegate :



بمجرد أن نقوم بذلك ، يبدأ المترجم في "الشكوى" ، وننقر على الدائرة الحمراء ونطلب منك: "هل تريد إضافة الطرق المطلوبة لبروتوكول UICollectionViewDragDelegate ؟"
أجبت: "بالطبع أريد!" وانقر على زر Fix :



الطريقة الوحيدة المطلوبة لبروتوكول UICollectionViewDragDelegate هي طريقة itemsForBeginning ، والتي ستخبر نظام Drag بأننا "نقوم بالسحب والإسقاط". يتم استدعاء طريقة itemsForBeginning عندما يبدأ المستخدم في "سحب" ( Dragging ) خلية في خلية المجموعة.

لاحظ أنه في هذه الطريقة ، قامت Collection View بإضافة indexPath . سيخبرنا هذا عن عنصر المجموعة ، أي indexPath ، سنقوم "بالسحب والإسقاط". هذا حقًا مناسب جدًا لنا ، حيث أن التطبيق هو المسؤول عن استخدام وسيطات الجلسة و indexPath لمعرفة كيفية التعامل مع هذا السحب والإفلات من Drag .

إذا تم إرجاع الصفيف [UIDragItems] للعناصر "القابلة للسحب " ، Drag تهيئة "سحب" Drag ؛ إذا تم إرجاع الصفيف الفارغ [] ، فسيتم تجاهل "سحب" Drag .

سوف أقوم بإنشاء دالة dragItems صغيرة خاصة (في: indexPath) مع وسيطة indexPath . تقوم بإرجاع الصفيف [UIDragItem] الذي نحتاج إليه .



كيف يبدو شكل السحب والإفلات UIDragItem ؟
لديه شيء واحد مهم للغاية يسمى itemProvider . itemProvider هو مجرد شيء يمكن أن يوفر البيانات التي سيتم سحبها.

ويحق لك أن تسأل: "ماذا عن" سحب وإسقاط "عنصر UIDragItem الذي لا يحتوي على بيانات ببساطة؟" قد لا يحتوي العنصر الذي تريد سحبه على بيانات ، على سبيل المثال ، لأن إنشاء هذه البيانات عملية مكلفة. قد تكون هذه صورة صورة أو شيء يتطلب تنزيل البيانات من الإنترنت. الشيء العظيم هو أن عملية Drag & Drop غير متزامنة تمامًا. عندما تبدأ في السحب والإسقاط ، يكون هذا الكائن خفيف الوزن للغاية ( lift preview ) ، تسحبه في كل مكان ولا يحدث أي شيء أثناء هذا "السحب". ولكن بمجرد "إسقاط" Drop ما في مكان ما ، فإن كونك عنصرًا في قائمة العناصر ، يجب أن يوفر لك العنصر "المسحوب " و " المطرود " ببيانات حقيقية ، حتى إذا استغرق الأمر قدرًا معينًا من الوقت.

لحسن الحظ ، هناك العديد من موفري العناصر المضمنة . هذه هي الفئات الموجودة بالفعل في iOS وهي itemPoviders ، على سبيل المثال ، NSString ، والذي يسمح لك بسحب النص وإفلاته بدون خطوط. بالطبع ، هذه صورة UIImage . يمكنك تحديد صور UIImages وسحبها وإفلاتها طوال الوقت . فئة NSURL ، وهي رائعة تمامًا. يمكنك الذهاب إلى صفحة Web ، وتحديد URL و "إسقاط" أينما تريد. يمكن أن يكون هذا رابطًا لمقالة أو URL لصورة ، كما سيكون في العرض التوضيحي الخاص بنا. هذه هي فئات الألوان من UIColor ، عنصر الخريطة MKMapItem ، جهة اتصال CNContact من دفتر العناوين ، يمكنك تحديد وسحب الكثير من الأشياء. كلهم من itemProviders .

سنقوم بسحب وإسقاط صورة UIImage . وهو موجود في خلية خلية Collection View مع indexPath ، مما يساعدني في تحديد خلية الخلية ، وإخراج صورة Outlet منها والحصول على صورتها .

دعونا نعبر عن هذه الفكرة مع سطرين من التعليمات البرمجية.
أولاً ، أطلب Collection View مجموعتي حول خلية لعنصر العنصر المقابل لهذا indexPath .



تعمل طريقة cellForItem (في: IndexPath) لطريقة Collection View للخلايا المرئية فقط ، ولكنها بالطبع ستعمل في حالتنا ، لأنني "أسحب وأفلت" عنصر مجموعة Drag على الشاشة وهو مرئي.

لذا ، حصلت على خلية خلية "قابلة للسحب".
بعد ذلك ، يمكنني استخدام عامل التشغيل ؟ لهذه الخلية بحيث تحتوي على نوع من subclass المخصصة الخاصة بي. وإذا كان هذا يعمل ، فأنا أحصل على صورة Outlet imageView ، والتي ألتقط منها صورتها . لقد قمت للتو "بالتقاط" صورة الصورة لهذا indexPath .

الآن بعد أن أصبحت لدي صورة صورة ، كل ما علي فعله هو إنشاء واحدة من عناصر UIDrag باستخدام عناصر الصورة الناتجة كعنصر توفير ، أي الشيء الذي يزودنا بالبيانات.
يمكنني إنشاء dragItem باستخدام مُنشئ UIDragItem ، الذي يأخذ itemProvider كوسيطة:



ثم نقوم بإنشاء itemProvider لصورة الصورة أيضًا باستخدام مُنشئ NSItemProvider . هناك العديد من منشئي NSItemProvider ، ولكن من بينهم واحد رائع حقًا - NSItemProvider (الكائن: NSItemProviderWriting) :



يمكنك ببساطة إعطاء كائن الكائن إلى مُنشئ NSItemProvider ، وهو يعرف كيفية إخراج itemProvider منه. على هذا النحو ، أعطي الصورة الصورة التي تلقيتها من خلية الخلية وأحصل على itemProvider لـ UIImage .
هذا كل ما في الأمر. أنشأنا dragItem ويجب أن نعيده كمصفوفة لها عنصر واحد.

ولكن قبل أن أعود dragItem ، أنا ذاهب لتفعل شيئا واحدا أكثر، ألا وهو تعيين متغير localObject ل dragItem ، أي ما يعادل الصورة الناتجة صورة .



ماذا يعني هذا؟
إذا قمت بإجراء "السحب والإفلات" Dragمحليًا ، أي داخل تطبيقك ، فلن تحتاج إلى مراجعة كل هذا الرمز المرتبط بـ itemProvider ، من خلال استرداد البيانات غير المتزامن. لا تحتاج للقيام بذلك ، كل ما عليك فعله هو استخدام localObject واستخدامه. هذا نوع من "الدائرة القصيرة" مع "السحب والإفلات" المحلي Drag.

ستعمل الشفرة التي كتبناها عند "السحب" Dragخارج مجموعتنا Collection Viewإلى تطبيقات أخرى ، ولكن إذا " سحبنا " Dragمحليًا ، يمكننا استخدام localObject . بعد ذلك ، أعيد مصفوفة من عنصر dragItem واحد .

بالمناسبة ، إذا لم أتمكن لسبب ما من الحصول على صورة لخلية الخلية هذه ، فعندئذٍ أعيد صفيفًا فارغًا [] ، وهذا يعني إلغاء "السحب والإفلات" Drag.



بالإضافة إلى وجوه المحلية localObject ، يمكنك أن نتذكر سياق محلي localContext لدينا Dragجلسة الدورة . في حالتنا سيكون عبارة عن مجموعة من collectionView ومن المفيد لنا بعد ذلك:



وجود "السحب والإسقاط" Drag، يمكنك إضافة المزيد من العناصر العناصر في هذا "السحب والإسقاط"، فقط تفعل لفتة الصنبور عليها. نتيجة لذلك ، يمكنك السحبDragالعديد من العناصر في وقت واحد. وهذا سهل التطبيق مع طريقة تفويض أخرى ، UICollectionViewDragDelegate ، تشبه إلى حد كبير طريقة itemsForeginning ، وهي طريقة تسمى itemsForAddingTo . تبدو طريقة itemsForAddingTo مطابقة تمامًا لطريقة itemsForeginning وتعيد نفس الشيء تمامًا ، لأنها تعطينا أيضًا فهرسًا لما "قام المستخدم" بتسجيله أثناء عملية "السحب والإفلات" Drag، وأحتاج فقط إلى الحصول على صورة الصورة من الخلية إلى التي قام المستخدم "بتسجيلها" وإعادتها.



إرجاع صفيف فارغ [] من أسلوب itemsForAddingToيؤدي إلى حقيقة أن إيماءة الصنبور سيتم تفسيرها بالطريقة المعتادة ، أي كخيار خلية الخلية هذه .
وهذا كل ما نحتاجه للسحب والإسقاط Drag.
أطلقنا التطبيق.
أختار صورة "البندقية" وأحتفظ بها لفترة وأبدأ في التحرك ...



ويمكننا حقًا سحب هذه الصورة إلى التطبيق Photos، كما ترى علامة الجمع الخضراء "+" في الزاوية اليسرى العليا من الصورة "القابلة للسحب". يمكنني تنفيذ إيماءة نقر على صورة Artika أخرى من المجموعة Collection View...



... والآن يمكننا إسقاط صورتين في التطبيق Photos:



نظرًا لأن Photosالآلية مضمنة بالفعل في التطبيقDrag & Dropثم كل شيء يعمل بشكل جيد وهو رائع.
لذلك، وأنا أعمل "شد الحبل" Dragو "إعادة تعيين" Dropمعرض الصورة لتطبيق آخر، لم يكن لدي الكثير لتفعله في طلبي، باستثناء تسليم صورة صورة كصفيف [UIDragItem] . هذه واحدة من العديد من الميزات الرائعة للآلية Drag & Drop- من السهل جدًا جعلها تعمل في كلا الاتجاهين.

إعادة تعيين Dropالصور إلى المجموعةCollection View


الآن نحن بحاجة إلى إنشاء Dropجزء لمجموعتي Collection Viewحتى نتمكن من "تفريغ" Dropأي صور "تم سحبها" داخل هذه المجموعة. يمكن أن تأتي "صورة قابلة للسحب" من الخارج ، أو مباشرة داخل هذه المجموعة.
للقيام بذلك، ونحن نفعل نفس الشيء فعله لتفويض dragDelegate ، أي جعل أنفسنا، الذات ، تفويض dropDelegate في طريقة لviewDidLoad :



مرة أخرى، علينا أن يصعد إلى أعلى طبقتنا ImageGalleryCollectionViewController والتحقق من تنفيذ بروتوكول UICollectionViewDropDelegate :



بمجرد إضافة بروتوكولنا الجديد ، بدأ المترجم مرة أخرى في "الشكوى" من أننا لم ننفذ هذا البروتوكول. نضغط على الزر Fixوتظهر الطرق المطلوبة لهذا البروتوكول أمامنا. في هذه الحالة ، يتم إعلامنا بأنه يجب علينا تنفيذ أسلوب PerformDrop :



يجب أن نقوم بذلك ، وإلا فلن تحدث "إعادة تعيين" Drop. في الواقع ، سأقوم بتطبيق أسلوب PerformDrop أخيرًا ، لأن هناك Appleطريقتان من الطرق الأخرى الموصى بها بشدة والتي تحتاج إلى تنفيذها Dropللجزء. ومن canHandle و dropSessionDidUpdate :



إذا قمنا بتطبيق هاتين الطريقتين ، فيمكننا الحصول على علامة صغيرة خضراء بالإضافة إلى علامة "+" عندما نقوم بسحب صور من الخارج إلى مجموعتنا ollection View، بالإضافة إلى ذلك ، لن يحاولوا التخلص مما لا نفهمه.

دعنا نطبق canHandle . نسختك من طريقة canHandle ، وهي مخصصة للتجميع ollection View، لكن هذه الطريقة ollection Viewتبدو تمامًا مثل الطريقة المماثلة لـ UIView العادية ، ولا يوجد indexPath هناك ، نحتاج فقط إلى إرجاع session.canLoadObjects (ofClass: UIImage.self) ، وهذا يعني أن أقبل "إعادة تعيين" كائنات هذا cl PAS في مجموعتي ollection View:



ولكن هذا لا يكفي "لإلقاء" Dropالصورة في مجموعتي Collection Viewخارج.
واذا كان "إعادة تعيين" Dropيأخذ صورة مكان داخل المجموعة Collection View، وسيكون المستخدم إعادة تنظيم العناصر الخاصة من البنود من خلال آلية Drag & Drop، ثم صورة واحدة فقط لUIImage ، وتطبيق الأسلوب canHandle سيبدو بالطريقة المذكورة أعلاه.

ولكن إذا حدث "تفريغ" Dropللصورة خارجًا ، فيجب علينا فقط التعامل مع "السحب والإفلات" Dragوهي صورة UIImage جنبًا إلى جنب مع URLهذه الصورة ، حيث لن نقوم بتخزين صور UIImage مباشرةفي النموذج. في هذه الحالة، سوف أعود صحيح في طريقة canHandle إلا إذا كان أداء كل الظروف بضع session.canLoadObjects (ofClass: NSURL.self) && session.canLoadObjects (ofClass: UIImage.self) :



لقد تركت لتحديد ما إذا أتعامل مع "إعادة تعيين" خارج أو داخل. سأفعل ذلك باستخدام الثابت المحسوب isSelf ، والذي يمكنني من خلاله حساب مثل هذا الشيء في Dropجلسة جلسة مثل Dragجلسة LocalDragSession للجلسة المحلية . هذه Dragالجلسة المحلية بدورها لها سياق محلي localContext .
إذا كنت تتذكر ، قمنا بتعيين هذا السياق المحلي في الطريقةitemsForVeginning Drag مندوب UICollectionViewDragDelegate :



انا ذاهب لاستكشاف السياق المحلي localContext في المساواة في مجموعتي CollectionView . صحيح نوع localContext إلى أي ، ولست بحاجة لجعل "الصب" اكتب أي استخدام المشغل على النحو؟ UICollectionView :



إذا كان السياق المحلي (session.localDragSession .localContext كما UICollectionView؟) هل مجموعتي CollectionView ، المتغير توسيع يعتمد على طريقة التعلم غير صحيحوهناك "إعادة تعيين" محلية داخل مجموعتي. إذا تم انتهاك هذه المساواة ، فإننا نتعامل مع "إعادة تعيين" Dropخارج. تفيد

طريقة canHandle أنه يمكننا فقط التعامل مع هذا النوع من "السحب والإفلات" Dragفي مجموعتنا Collection View. خلاف ذلك ، علاوة على ذلك ، لا معنى للحديث عن "الإغراق" Drop.

إذا واصلنا "إعادة تعيين" Drop، فإنه لا يزال حتى اللحظة أن المستخدم سوف ترفع أصابعك خارج الشاشة وسوف يكون هناك "إعادة تعيين" حقيقية Drop، علينا أن تقرير iOSباستخدام طريقة dropSessionDidUpdate مندوب UICollectionViewDropDelegate عن عرضنا UIDropProposal لتنفيذ إعادة تعيين Drop.

في هذه الطريقة ، يجب أن نعيد Dropجملة يمكن أن تحتوي على قيم .copy أو .move أو .cancel أو. ممنوع لوسيطة العملية . وهذه كلها الاحتمالات التي لدينا في الحالة العادية عند التعامل مع UIView العادي .

لكن المجموعة Collection Viewتذهب إلى أبعد من ذلك وتقدم عروض لإعادة العرض المتخصص UICollectionViewDropProposal ، وهو subclassفئة من UIDropProposal ويسمح لك بتحديد ، بالإضافة إلى عملية التشغيل ، معلمة intent إضافية للمجموعة Collection View.

معلمةتخبر intent المجموعةCollection Viewما إذا كنا نريد وضع العنصر "المهمَل" داخل خلية موجودة ، أو ما إذا كنا نريد إضافة خلية جديدة . هل ترى الفرق؟ في حالة المجموعة ،Collection Viewيجب أن ننوي نيتنا .

في حالتنا ، نريد دائمًا إضافة خلية جديدة ، لذلك سترى ما ستكون معلمة intent الخاصة بنا متساوية.
نختار المُنشئ الثاني لـ UICollectionViewDropProposal :



في حالتنا ، نريد دائمًا إضافة خلية جديدةوستأخذمعلمة intent القيمة .insertAtDestinationIndexPath بدلاً من ذلك.insertIntoDestinationIndexPath .



لقد استخدمت مرة أخرى الثابت المحسوب هو نفسه ، وإذا كانتإعادة تنظيم ذاتي ، فأنا أقوم بنقل .move ، وإلا فإنني أقوم بنسخ .copy . في كلتا الحالتين ، نستخدم .insertAtDestinationIndexPath ، أي إدخال خلايا جديدة.

حتى الآن لم أقم بتنفيذ أسلوب PerformDrop ، ولكن دعونا نلقي نظرة على ما يمكن أن تفعله مجموعة بالفعلCollection Viewبهذه القطعة الصغيرة من المعلومات التي قدمناها لها.

اسحب الصورة منSafariمحرك البحثGoogle، وتظهر علامة "+" خضراء أعلى هذه الصورة ، تشير إلى أن معرض الصور الخاص بنا ليس جاهزًا لقبول هذه الصورة ونسخها معها URLفحسب ، بل يوفر أيضًا مكانًا داخل المجموعة Collection View:



يمكنني النقر فوق زوج آخر من الصور في Safari، و ستكون هناك بالفعل 3 صور "تم سحبها":



ولكن إذا رفعت إصبعي و "أسقطت" Dropهذه الصور ، فلن يتم وضعها في معرض الصور الخاص بنا ، ولكن ببساطة العودة إلى أماكنها السابقة ، لأننا لم نقم بعد بتنفيذ أسلوب PerformDrop .



يمكنك أن ترى أن المجموعة Collection Viewتعرف بالفعل ما أريد القيام به.
المجموعة Collection Viewهي شيء رائع للغاية بالنسبة للآلية.Drag & Drop، لديها وظائف قوية للغاية لهذا الغرض. بالكاد لمسناها بكتابة 4 أسطر من التعليمات البرمجية ، وقد انتقلت بالفعل إلى حد بعيد في تصور "إعادة الضبط" Drop.
دعنا نعود إلى الكود ونطبق طريقة PerformDrop .



في هذه الطريقة ، لن نتمكن من الحصول على 4 أسطر من التعليمات البرمجية ، لأن طريقة PerformDrop أكثر تعقيدًا قليلاً ، ولكن ليس كثيرًا.
عندما "إعادة تعيين" Drop، ثم طريقة performDrop نحن بحاجة لتحديث نموذجنا، وهو معرض للصور imageGallery صورة السرد الصور ، ونحن بحاجة إلى تحديث لدينا مجموعة البصرية CollectionView .

لدينا سيناريوهان مختلفان "لإعادة التعيين" Drop.

إذا كان هناك "إعادة تعيين" Dropمن مجموعة collectionView الخاصة بي ، فيجب علي "إعادة تعيين" Dropعنصر المجموعة في مكان جديد وإزالته من المكان القديم ، لأنني في هذه الحالة أنقل ( .move ) عنصر المجموعة هذا. هذه مهمة تافهة.

هناك "إعادة تعيين" Dropمن تطبيق آخر ، ثم يجب علينا استخدام خاصية itemProvider لعنصر العنصر " المسحوب " لتحديد البيانات.

عندما نقوم بإجراء "إعادة تعيين" Dropفي مجموعة collectionView ، تزودنا المجموعة بمنسق منسق. أولا وقبل كل شيء، نحن أفاد منسق منسق ، فإنه destinationIndexPath ، أي indexPath "-destination"، "إعادة تعيين" Drop، وهذا هو المكان الذي سيكون "إعادة تعيين".



ولكن قد يكون الوجهة الوجهة indexPath صفريًا ، حيث يمكنك سحب الصورة "المهملة" إلى ذلك الجزء من المجموعة Collection Viewالذي ليس المكان بين بعض الخلايا الموجودة ، لذلك يمكن أن تكون صفرية . إذا حدث هذا الموقف ، فأنا أقوم بإنشاء مسار مسار باستخدام عنصر العنصر 0 في قسم القسم 0 .



يمكنني اختيار أي indexPath آخر ، لكنني سأستخدم هذا indexPath بشكل افتراضي.

الآن نعرف أين سنقوم بإجراء "إعادة التعيين" Drop. علينا أن نمر عبر جميع عناصر المنسق "القابلة لإعادة التوطين" المقدمة من المنسق . يحتوي كل عنصر من هذه القائمة على نوع UICollectionViewDropItem ويمكنه تزويدنا بمعلومات مثيرة جدًا للاهتمام.

على سبيل المثال، اذا كان يمكنني الحصول sourceIndexPath من item.sourceIndexPath ، أنا أعرف بالضبط ما هو عليه "السحب والإسقاط" Dragيتم في حد ذاته، الذات، ومصدر السحب Dragهو عنصر المجموعة مع indexPath يساوي sourceIndexPath :



لست مضطرًا حتى إلى إلقاء نظرة على localContext في هذه الحالة لمعرفة أن هذا "السحب والإفلات" تم داخل مجموعة collectionView . واو!

الآن أعرف المصدر sourceIndexPath ووجهة " الوجهة " الوجهة indindPathDrag & Drop ، وتصبح المهمة تافهة. كل حاجة I القيام به هو تحديث نموذج بحيث المصدر و"نقطة المقصد" يتم عكسها، ومن ثم تحديث جمع CollectionView ، والتي سوف تحتاج إلى إزالة عنصر من مجموعة sourceIndexPath وإضافته إلى جمع destinationIndexPath .

حالتنا المحلية هي الأبسط ، لأنه في هذه الحالة Drag & Dropلا تعمل الآلية في نفس التطبيق فقط ، ولكن في نفس المجموعة collectionView ، ويمكنني الحصول على جميع المعلومات اللازمة باستخدام منسق المنسق. دعنا ننفذها في هذه الحالة المحلية الأبسط:



في حالتنا ، لا أحتاج حتى LocalObject ، الذي قمت "بإخفائه" في وقت سابق عندما قمت بإنشاء dragItem والذي يمكنني الآن استعارته من العنصر " المسحوب " في مجموعة العناصر في نموذج item.localObject . سنحتاج إليها عند "إغراق" Dropالصور في "سلة المهملات" ، الموجودة في نفس التطبيق ، ولكنها ليست نفس مجموعة collectionView . الان انا بحاجة اثنين فقط IndexPathes : مصدر sourceIndexPath و "نقطة المقصد" destinationIndexPath .

أحصل على المعلومات أولاًimageInfo عن الصورة في المكان القديم من النموذج وإزالتها من هناك. ثم تضاف إلى مجموعة من الصور من بلدي نماذج imageGallery المعلومات imageInfo صورة مع مؤشر جديد destinationIndexPath.item . هذه هي الطريقة التي قمت بها بتحديث النموذج الخاص بي:



الآن يجب أن أقوم بتحديث مجموعة collectionView نفسها. من المهم أن نفهم أن كنت لا تريد أن تفرط كافة البيانات في مجموعتي collectionView باستخدام reloadData () في وسط "السحب والإسقاط"Drag، لأنه يعيد "العالم" كله من معرضنا من الصور، التي هي سيئة جدا، وأنا لا. بدلاً من ذلك ، سأقوم بتنظيم العناصر وإدراجهاالعناصر بشكل فردي:



قمت بحذف عنصر مجموعة collectionView مع sourceIndexPath وأدرجت عنصر مجموعة جديدًا مع destinationIndexPath .

يبدو أن هذا الرمز يعمل بشكل رائع ، ولكن في الواقع ، يمكن لهذا الرمز "تعطل" تطبيقك. والسبب هو أنك تقوم بإجراء العديد من التغييرات على مجموعة collectionView الخاصة بك، وفي هذه الحالة يجب أن تتم مزامنة كل خطوة من خطوات تغيير المجموعة مع النموذج بشكل طبيعي ، وهو ما لا تتم ملاحظته في حالتنا ، حيث نقوم بإجراء العمليتين في نفس الوقت: الحذف والإدراج. ومن هنا جمع collectionViewسيكون في مرحلة ما في حالة غير متزامنة مع النموذج.

ولكن هناك طريقة رائعة حقًا للتغلب على هذا ، وهي أن مجموعة collectionView لديها طريقة تسمى PerformBatchUpdates لها إغلاق ( closure) وداخل هذا الإغلاق يمكنني وضع أي عدد من هذه العناصر ، و insertItems ، و moveItems ، وكل ما أريده:



الآن سيتم حذف deleteItems و insertItems كعملية واحدة ، ولن يكون هناك نقص في مزامنة النموذج الخاص بك مع collection collectionView .

وأخيرًا ، آخر ما نحتاج إلى فعله هو أن تطلب من المنسق تنفيذ "إعادة التعيين" وتحريكها Drop:



بمجرد رفع إصبعك من الشاشة ، تتحرك الصورة ، كل شيء يحدث في نفس الوقت: "إعادة التعيين" ، تختفي الصورة في مكان ومظهر في مكان آخر.
دعونا نحاول نقل صورة الاختبار "البندقية" في معرض الصور الخاص بنا إلى نهاية السطر الأول ...



... "وإعادة تعيينها":



كما أردنا ، فقد تم وضعها في نهاية السطر الأول.
مرحى!كل شيء يعمل!

الآن لن نتعامل مع الحالة المحلية ، أي عندما يأتي عنصر "إعادة التعيين" خارج ، أي من تطبيق آخر.
للقيام بذلك ، نكتب آخر في الكود فيما يتعلق sourceIndexPath . إذا لم يكن لدينا sourceIndexPath ، فهذا يعني أن عنصر "إعادة التعيين" جاء من الخارج وسيتعين علينا استخدام نقل البيانات باستخدام itemProver لعنصر resettable item.dragItem.itemProvider :



إذا قمت "بالسحب والإفلات" Dragخارج و "إسقاط" "Dropثم هل تصبح هذه المعلومات متاحة على الفور؟ لا ، أنت تحدد البيانات من الشيء "الذي تم سحبه" بشكل غير صحيح. ولكن ماذا لو كانت العينة تستغرق 10 ثوانٍ؟ ماذا ستفعل المجموعة في هذا الوقت ollection View؟ بالإضافة إلى ذلك ، قد لا تصل البيانات بالترتيب الذي طلبناه به. لإدارة هذا ليس بالأمر السهل ، Appleواقترح ollection Viewلهذه الحالة تقنية جديدة تمامًا لاستخدام البدائل Placeholders.

أنت تضع Collection Viewعنصرًا نائبًا في مجموعتك Placeholder، Collection Viewوتدير المجموعة كل شيء نيابة عنك ، لذلك كل ما عليك فعله عند تحديد البيانات أخيرًا هو أن تطلب من العنصر النائب Placeholderالاتصال بالعنصر النائب سياق السياق الخاص بهوأخبره أنك تلقيت المعلومات. بعد ذلك ، قم بتحديث العنصر النائب الخاص بالنموذج والسياق ، حيث يقوم تلقائيًا بتبديل الخلية بالعنصر النائب Placeholderبإحدى الخلايا الخاصة بك ، وهو ما يتوافق مع نوع البيانات التي تلقيتها.

نقوم بتنفيذ كل هذه الإجراءات من خلال إنشاء سياق عنصر نائب PlaceContext يدير العنصر النائب Placeholderوالذي تحصل عليه من منسق المنسق ، ويطلب منك "إعادة تعيين" عنصرDrop العنصر إلى العنصر النائب . سأستخدم المُهيئ لسياق العنصر النائب للعنصر النائبPlaceholder

الذي "يلقي" dragItem على UICollectionViewDropPlaceholder :



الكائن الذي أنا ذاهب إلى "رمي" Dropأنه item.dragItem ، حيث البند - عنصر لل دورة، ونحن يمكن أن "رمي" Dropمجموعة من الأشياء coordinator.items . نرميها واحدة تلو الأخرى. إذن item.dragItem هو ما " نسحبهDrag " Drop. الوسيطة التالية لهذه الوظيفة هي العنصر النائب ، وسأقوم بإنشائها باستخدام مُهيئ UICollectionViewDropPlaceholder :



للقيام بذلك ، أحتاج إلى معرفة أين سأقوم بإدراج العنصر النائبPlaceholder، مثل insertionIndexPath ، بالإضافة إلى معرف خلية reuseIdentifier .
من الواضح أن الوسيطة insertionIndexPath تساوي الوجهة الوجهة indindPath ، وهي IndexPath لوضع الكائن "الذي تم سحبه" ، ويتم حسابه في بداية أسلوب PerformDropWith .

الآن دعونا نلقي نظرة على معرف خلية reuseIdentifier . يجب عليك تحديد نوع خلية الخلية التي يحددها محدد موقعك Placeholder. ليس لدى المنسق المنسق خلية " معبأة مسبقًا" لتحديد الموقعPlaceholder. أنت من يجب أن يقرر خلية الخلية هذه . لذلك ، مطلوب معرف معرف reuseIdentifiercell الخلية المعاد استخدامها الخاص بك storyboardبحيث يمكن استخدامه كنوع PROTOTYPE.

سوف أسميها "DropPlaceholderCell" ، ولكن في الأساس يمكنني تسميتها أيا كان.
هذه مجرد سلسلة السلسلة التي سأستخدمها في الأعمال المتعلقة بي storyboardلإنشاء هذا الشيء.
عد إلى بلدنا storyboardوقم بإنشاء خلية خلية للعنصر النائب Placeholder. للقيام بذلك ، نحتاج فقط إلى تحديد مجموعة Collection Viewوفحصها. في المجال الأول ، Itemsأتغير 1إلى2. يؤدي هذا على الفور إلى إنشاء خلية ثانية لنا ، وهي نسخة طبق الأصل من الخلية الأولى.



نختار خليتنا الجديدة ImageCell، ونعين المعرف " DropPlaceholderCell" ، ونحذف جميع UIالعناصر من هناك ، بما في ذلك Image View، نظرًا لأن هذا PROTOTYPE يستخدم عندما لا تصل الصورة بعد. نضيف مؤشر نشاط جديد من لوحة الكائن هناك Activity Indicator، وسوف يتم تدويره ، مما يتيح للمستخدمين معرفة أنني أتوقع بعض بيانات "إعادة التعيين". أيضا تغيير لون الخلفية Backgroundلفهم أنه عندما "إعادة تعيين" من الصورة خارج يعمل بالضبط هذه الخلية خلية كنماذج أولية:



بالإضافة إلى نوع من الخلايا الجديدة لا ينبغي أن يكون ImageCollectionVewCell، لأنه لن يكون هناك صور فيه. سأجعل هذه الخلية خلية UIollectionCiewCell TYPE عادية ، لأننا لا نحتاج إلى أي Outletsعنصر تحكم:



دعنا نقوم بتكوين مؤشر النشاط Activity Indicatorبحيث يبدأ في الحركة من البداية ، ولن أضطر إلى كتابة أي شيء في الرمز لبدء تشغيله. للقيام بذلك ، انقر فوق الخيار Animating:



وهذا كل شيء. لذا ، قمنا بعمل جميع الإعدادات لهذه الخلية DropPlaceholderCell، ونعود إلى الكود الخاص بنا. الآن لدينا محدد رائع Placeholderجاهز للذهاب .

كل ما علينا فعله هو الحصول على البيانات ، وعندما يتم استلام البيانات ، فإننا فقط نخبر العنصر النائب عن هذا السياق ، وسوف نتبادل العنصر النائبPlaceholderوالخلية "الأصلية" الخاصة بنا مع البيانات ، وسنقوم بإجراء تغييرات على النموذج.

انا ذاهب الى "تحميل" كائن واحد ، والذي سيكون البند الخاص بي باستخدام طريقة loadObject (ofClass: UIImage.self) (المفرد). يمكنني استخدام رمز item.dragItem.itemProvider المورد itemProvider ، والتي سوف توفر عناصر البيانات لديها البند بشكل غير متزامن. من الواضح أنه إذا كان iitemProvider متصلاً ، فإننا نحصل على كائن "إعادة تعيين" iitem خارج هذا التطبيق. فيما يلي طريقة loadObject (ofClass: UIImage.self) (المفرد):



لم يتم تنفيذ هذا الإغلاق الخاص فيmain queue. ولسوء الحظ ، كان علينا التبديل إلى main queueاستخدام DispatchQueue.main.async {} من أجل "التقاط" نسبة العرض إلى الارتفاع للصورة في متغير الجوانب المحلي .

نحن حقا أدخلت اثنين من المتغيرات المحلية عنوان الصورة و aspectRatio ...



... وسوف "الصيد" لهم تحميل الصور صورة وURL رابط لا :



إذا كان اثنان من المتغيرات المحلية عنوان الصورة و aspectRatio لا يساوي صفرا ، وسوف نطلب سياق نائب placeholderSontext باستخدام طريقة commitInsertionامنحنا الفرصة لتغيير نموذج معرض الصور الخاص بنا :



في هذا التعبير ، لدينا insertionIndexPath - هذا هو indexPath لإدخاله ، وقمنا بتغيير نموذج معرض الصور الخاص بنا . هذا كل ما عليك القيام به، وهذه الطريقة سوف تحل تلقائيا نائبا Placeholderعلى الخلية خلية من الطبيعي استدعاء أسلوب cellForItemAt .

لاحظ أن insertionIndexPath يمكن أن يكون مختلفًا تمامًا عن destinationIndexPath . لماذا؟نظرًا لأن أخذ عينات البيانات قد يستغرق 10 ثوان ، بالطبع ، فمن غير المحتمل ، ولكن قد يستغرق 10 ثوان. خلال هذا الوقت ، Collection Viewيمكن أن يحدث الكثير في المجموعة . يمكن إضافة خلايا جديدة الخلايا ، كل شيء يحدث بما فيه الكفاية سريعة.

استخدم دائمًا insertionIndexPath ، و insertionIndexPath فقط ، لتحديث نموذجك.

كيف نقوم بتحديث نموذجنا؟

نحن تضاف الى مجموعة imageGallery.images هيكل imagemodel ، ويتألف من نسبة الارتفاع aspectRatio وصورة URL عنوان الصورة ، الذي قدم لنا دعم المقابلة من قبل مزود .

يعمل هذا على تحديث نموذج imageGallery الخاص بنا ، وتفي لنا طريقة الالتزام بالاعتماد . لم تعد بحاجة إلى القيام بأي شيء إضافي ، ولا إدراج ، ولا حذف ، ولا شيء من هذا. وبالطبع ، بما أننا في إغلاق ، نحتاج إلى إضافة الذات. .



إذا أردنا لسبب غير قادرين على الحصول على نسبة الارتفاع aspectRatio و URLالصورة عنوان الصورة من المقابلة قبل مزود ، قد وردت خطأ خطأ بدلا من مزود ، لدينا ليعرفوا سياق placeholderContext ، تحتاج إلى تدمير هذا عنصر نائب Placeholder، لأننا كل نفس، وأننا لا نستطيع الحصول على بيانات أخرى:



هناك شيء واحد يجب أخذه في الاعتبار هو URLsأنها تأتي من أماكن مثل هذه Google؛ في الواقع ، يحتاجون إلى تحويلات طفيفة للحصول على "نظيف"URLللصورة. يمكن رؤية كيفية حل هذه المشكلة في هذا التطبيق التجريبي في ملف Utilities.swiftعلى Github .
لذلك ، عند تلقي URLصورة ، نستخدم خاصية imageURL من فئة عنوان URL :



وهذا كل ما عليك فعله لقبول شيء ما خارج المجموعة داخل المجموعة Collection View.

دعونا نراها في العمل. تشغيل تعدد المهام في وقت واحد لدينا التطبيق التجريبي ImageGalleryو Safariمحرك البحث Google. في Googleالبحث عن صور لموضوع "الفجر" (شروق الشمس). في Safariبني بالفعلDrag & Dropالآلية ، حتى نتمكن من تحديد إحدى هذه الصور ، والاحتفاظ بها لفترة طويلة ، ونقلها قليلاً وسحبها إلى معرض الصور الخاص بنا.



يشير وجود علامة الجمع الخضراء "+" إلى أن تطبيقنا جاهز لقبول صورة طرف ثالث ونسخها إلى مجموعتك في الموقع المحدد من قبل المستخدم. بعد "إعادة تعيين" الصورة ، يستغرق الأمر بعض الوقت لتنزيل الصورة ، وهي تعمل في الوقت الحالي Placeholder:



بعد اكتمال التنزيل ، يتم وضع صورة "إعادة التعيين" في المكان الصحيح ، Placeholderوتختفي:



يمكننا الاستمرار في "إعادة تعيين" الصور والمكان في مجموعات المزيد من الصور:



بعد عمل "إعادة الضبط" Placeholder:



نتيجة لذلك ، يمتلئ معرض الصور لدينا بصور جديدة:



الآن أنه من الواضح أننا قادرون على التقاط الصور من الخارج، ونحن لسنا بحاجة لاختبار صورة، وسنقوم بإزالة:



لدينا viewDidLoad يصبح في غاية البساطة: فهو نقوم به لدينا Controller Dragو Dropتفويض وإضافة التعرف على لفتة قرصة ، الذي ينظم عدد من الصور في كل سطر:



بالطبع ، يمكننا إضافة ذاكرة تخزين مؤقت لصور imageCache :



سنقوم بتعبئة imageCache عند "إعادة التعيين " Dropفي أسلوب PerformDrop ...



وعند الجلب من "الشبكة" في فئة ImageCollectionViewCell المخصصة : وسنستخدم



ذاكرة التخزين المؤقت imageCache عند تشغيل خليةخلية معرض الصور الخاص بنا في الفئة المخصصة ImageCollectionViewCell :



الآن نبدأ بمجموعة فارغة ...



... ثم "إسقاط" صورة جديدة في مجموعتنا ...



... يتم تحميل الصورةPlaceholderوتعمل ...



... وتظهر الصورة في المكان الصحيح:



نواصل ملء مجموعتنا خارج:



يأتي تحميل الصورPlaceholdersوالعمل ...



وتظهر الصور في المكان الصحيح:



لذا ، يمكننا أن نفعل الكثير مع معرض الصور الخاص بنا: املأه خارجًا ، وأعد تنظيم العناصر بالداخل ، وشارك الصور مع التطبيقات الأخرى نيامي.
نحتاج فقط إلى تعليمها كيفية التخلص من الصور غير الضرورية من خلال "إعادة تعيينها"Dropفي "سلة المهملات" المقدمة على شريط التنقل على اليمين. كما هو موضح في قسم "ميزات التطبيق التجريبي لمعرض الصور " ، يتم تمثيل "سلة المهملات" بفئة GabageView ، التي ترث من UIView ويجب أن نعلمها قبول الصور من مجموعتنا ollection View.

إعادة تعيين Dropصور المعرض إلى سلة المهملات.


مباشرة من المكان - إلى المحجر. وأود أن أضيف إلى GabageView "التفاعل" التفاعل ، وسوف UIDropInteraction ، لأنني أحاول أن "إعادة تعيين" Dropبعض الأشياء. كل ما نحتاجه لتقديم UIDropInteraction هو مفوض مفوض ، وسأعين أنفسنا ذاتيًا لهذا المفوض المفوض :



بطبيعة الحال ، يجب أن يؤكد فصل GabageView أننا ننفذ بروتوكول UIDropInteractionDelegate :



كل ما علينا القيام به لتشغيلهDrop ، هذا هو تنفيذ أساليب canHandle المعروفة لنا بالفعل ،sessionDidUpdate وأداء Drop .



ومع ذلك ، على عكس الطرق المماثلة للمجموعةCollection View، ليس لدينا أي معلومات إضافية في شكل indexPath لمكان الإغراق.

دعونا نطبق هذه الأساليب.
داخل طريقة canHandle سيتم معالجتها فقط "السحب والإسقاط"Drag، والتي تمثل صورة لUIImage . لذلك ، سأعيد true فقط إذا كانت session.canLoadObjects (ofClass: UIImage.self) :



في طريقة canHandle ،يمكنك فقط أن تقول بشكل أساسي أنه إذا كان الكائن "القابل للسحب" ليس صورة UIImage، ثم لا معنى لمواصلة إسقاط "إعادة" واستدعاء الأساليب اللاحقة.
إذا كان الكائن "القابل للسحب" هو صورة UIImage ، فسنقوم بتنفيذ طريقة sessionDidUpdate . كل ما علينا القيام به في هذه الطريقة هو إرجاع عرض "إعادة تعيين" UIDropProposal الخاص بنا Drop. وأنا على استعداد لقبول الكائن المحلي "المسحوب" فقط لـ UIImage TYPE للصورة ، والذي يمكن "إسقاطه" في Dropأي مكان داخل GarbageView . بلدي GarbageView لن تتفاعل مع الصور، ملقاة خارج. لذلك أقوم بالتحليل باستخدام متغير session.localDragSessionما إذا كان هناك "إعادة تعيين" محلي Drop، وأعيد جملة "إعادة تعيين" في شكل مُنشئ UIDropProposal مع وسيطة العملية التي تأخذ القيمة .copy ، لأن ALWAYS LOCAL "السحب والإفلات" Dragفي طلبي سيأتي من المجموعة Collection View. إذا كان هناك "السحب والإسقاط" Dragو "إعادة تعيين" Dropمن الخارج، ثم أعود لعرض "إعادة تعيين" في شكل مصمم UIDropProposal حجة العملية ، وتلقي قيمة .fobbiden ، وهذا هو "ممنوع" ونحن بدلا من اللون الأخضر علامة زائد "+" الحصول على علامة حظر "إعادة تعيين" .



نسخ صورة UIImage، سنقوم بمحاكاة انخفاض في مقياسه إلى ما يقرب من 0 ، وعندما تحدث "إعادة الضبط" ، سنقوم بإزالة هذه الصورة من المجموعة Collection View.
من أجل خلق الوهم بالصور "الإغراق والاختفاء" في "سلة المهملات" للمستخدم ، نستخدم طريقة معاينةForDropping ، جديدة علينا ، والتي تسمح لنا بإعادة توجيه "الإغراق" Dropإلى مكان آخر وفي نفس الوقت تحويل الكائن "الملغى" أثناء الرسوم المتحركة:



في هذه الطريقة ، باستخدام مُهيئ UIDragPreviewTarget ، نحصل على عرض مسبق جديد للكائن الهدف ليتم إسقاطه وإعادة توجيهه باستخدام طريقة retargetedPreviewإلى مكان جديد ، إلى "علبة القمامة" ، مع انخفاض حجمها إلى الصفر تقريبًا:



إذا رفع المستخدم إصبعه لأعلى ، عندها تحدث "إعادة الضبط" Drop، وأنا (مثل GarbageView ) أتلقى رسالة PerformDrop . في رسالة PerformDrop ، نقوم بإجراء "إعادة الضبط" الفعلية Drop. بصراحة ، الصورة التي تم إلقاءها على GarbageView نفسها لم تعد تهمنا ، لأننا سنجعلها غير مرئية عمليا ، على الأرجح أن حقيقة الانتهاء من "إعادة الضبط" Dropستشير إلى أننا نزيل هذه الصورة من المجموعة Collection View. ولتحقيق ذلك، نحن بحاجة إلى معرفة kollektsiiyu جدا جمع و indexPathالصورة المهملة فيه. من أين نأتي بهم؟

لأن عملية Drag & Dropتجري في تطبيق واحد، وهي متوفرة لنا جميعا المحلي: المحلي Dragجلسة localDragSession لدينا Dropجلسة الدورة ، والسياق المحلي localContext ، وليس لدينا مجموعة من sollectionView وكائن محلي localObject صورة إعادة تعيين، والذي يمكن أن نفعله في حد ذاته هو صورة من "معرض" أو indexPath . وبسبب هذا فإننا يمكن أن تحصل في طريقة performDrop الطبقة GarbageView جمع جمع ، واستخدامهمصدر بيانات كيف ImageGalleryCollectionViewController ونموذج imageGallery لديناController، يمكننا الحصول على مجموعة من الصور من الصور نوع [ImageModel]:



مع مساعدة من المحليةDragجلسة localDragSession لديناDropجلسة جلسة تمكنا من الحصول على جميع "السحب" على GarbageView Drag عناصر من البنود ، ويمكن أن يكون هناك الكثير، كما نعلم، و كانت الصور لدينا kolllektsii CollectionView . خلقDragعناصر dragItems مجموعتناCollection View، قدمنا لكل "السحب"DragعنصرdragItem الكائن المحلية localObject ، الذي هو صورة من الصورة ، ولكننا نحن لا تأتي في متناول اليدين أثناء جمع إعادة التنظيم الداخلي CollectionView ، ولكن "إعادة تعيين" معرض الصور "سلة المهملات" نحن بحاجة ماسة في منشأة المحلية localObject "السحب" وجوه dragItem ، بعد كل هذا الوقت ليس لدينا منسق يشارك بسخاء المعلومات حول ما يحدث في مجموعة collectionView . لذلك، نحن نريد كائن محلي localObject وكان مؤشر indexPath في صورة مجموعة صور من نماذجناimageGallery . إجراء التغييرات اللازمة في أسلوب dragItems (في indexPath: IndexPath) الطبقة ImageGalleryCollectionViewController :



الآن يمكن أن نتخذها كل "pretaskivaemogo" عنصر البند هو localObject ، وهو مؤشر indexPath في صورة مجموعة صور من نماذجنا imagegallery ، وإرسالها إلى المؤشرات مجموعة فهارس و مجموعة indexPahes صور الحذف:



معرفة مؤشر مجموعة فهارس ومجموعة indexPahes صور الحذف في طريقة performBatchUpdatesجمع جمع علينا إزالة جميع الصور المحذوفة من نماذج الصور ومن جمع جمع :



تشغيل التطبيق، وملء مع معرض صور جديدة:



اختيار زوج من الصور التي نريد إزالته من معرض لدينا ...



... "رمي" لهم على أيقونة مع "صندوق للقمامة" ...



أنها خفضت تقريبا إلى 0 ...



... وتختفي من المجموعة Collection View، مختبئين في "سلة المهملات":



حفظ الصور بين البدايات.



لحفظ معرض الصور بين عمليات التشغيل ، سنستخدم UserDefaults ، بعد تحويل نموذجنا إلى JSONتنسيق. للقيام بذلك، ونحن سوف تضيف لدينا Controllerمتغير defailts فار ...



... وفي نموذج ImageGallery و ImageModel بروتوكول قابل للترميز :



سلسلة سلسلة ، صفائف صفيف ، وURL ، و نقرا بالفعل على تنفيذ بروتوكول قابل للترميز ، وبالتالي ليس لدينا أي شيء آخر القيام به للحصول على الترميز العمل وفك ترميز نماذج ImageGallery في JSONالتنسيق.
كيف نحصل على JSONنسخة ImageGallery ؟
للقيام بذلك ، قم بإنشاء متغير محسوب var json ، والذي يُرجع نتيجة محاولة تحويل نفسه ، ذاتيًا ، باستخدام JSONEncoder.encode () إلى JSONالتنسيق:



وهذا كل شيء. سيتم إرجاع البيانات إما نتيجة تحويل الذات إلى تنسيق JSON، أو لا شيء إذا فشل هذا التحويل ، على الرغم من أن هذا التحويل لا يحدث أبدًا ، لأن هذا النوع قابل للتشفير بنسبة 100٪ . تستخدم اختياري متغير سلمان فقط لأسباب التماثل.
الآن لدينا طريقة لتحويل نماذج ImageGallery إلى تنسيق البياناتJSON . هل يحتوي متغير json على بيانات TYPE ؟ والتي يمكن تذكرها في UserDefaults .
تخيل الآن أننا بطريقة ما تمكنا من الحصول على JSONبيانات json ، وأود أن أعيد إنشاء نموذجنا منها ، وهو مثال لبنية ImageGallery . للقيام بذلك ، من السهل جدًا كتابة INITIALIZER لـ ImageGallery ، الذي تكون وسيطته إدخال JSONبيانات json . سيكون هذا المُهيئ مُهيئ "السقوط" (failable) إذا فشلت في التهيئة ، فإنها تتعطل وتعيد صفرًا :



أحصل على قيمة newValue باستخدام وحدة فك ترميز JSONDecoder ، أحاول فك تشفير بيانات json التي تم تمريرها إلى المُهيئ ، ثم تعيينها ذاتيًا .
إذا تمكنت من القيام بذلك ، فعندئذ أحصل على نسخة جديدة من ImageGallery ، ولكن إذا فشلت محاولتي ، فعندئذٍ أعود صفر ، حيث فشلت التهيئة الخاصة بي.
يجب أن أقول أنه لدينا هنا أسباب أكثر بكثير لـ "الفشل" ( fail) ، لأنه من الممكن أن تكون JSONبيانات jsonيمكن أن يفسد أو فارغًا ، كل هذا يمكن أن يؤدي إلى "سقوط" ( fail) المُهيئ.

الآن يمكننا تنفيذ READ JSONالبيانات ونموذج استرداد imagegallery طريقة viewWillAppear لدينا Controller...



... وكذلك إدخال في المراقب didSet {} خصائص imagegallery :



ودعونا تشغيل التطبيق وملء لدينا معرض للصور:



إذا كنا إغلاق التطبيق وفتحه مرة أخرى، يمكننا أن نرى معرضنا السابق تم حفظ الصور في UserDefaults .

الخلاصة


توضح هذه المقالة مدى سهولة دمج التكنولوجيا Drag & Dropفي iOSتطبيق باستخدام مثال التطبيق التجريبي البسيط "معرض الصور" . هذا جعل من الممكن تحرير معرض الصور بالكامل ، و "رمي" الصور الجديدة من التطبيقات الأخرى هناك ، ونقل التطبيقات الموجودة وحذف التطبيقات غير الضرورية. وكذلك لتوزيع الصور المتراكمة في المعرض على تطبيقات أخرى.

بالطبع ، نود إنشاء العديد من هذه المجموعات الخلابة المواضيعية للصور وحفظها مباشرة على iPad أو iCloud Drive. يمكن القيام بذلك إذا تم تفسير كل معرض من هذا القبيل على أنه UIDocument المخزنة بشكل دائم. سيسمح لنا هذا التفسير بالارتقاء إلى المستوى التالي من التجريد وإنشاء تطبيق يعمل مع المستندات. في مثل هذا التطبيق ، سيتم عرض المستندات الخاصة بك بواسطة مكون DocumentBrowserViewController ، وهو مشابه جدًا للتطبيق Files. سيسمح لك بإنشاء صور UIDocument من نوع "معرض الصور" على كل من الخاص بك iPadوعلى iCloud Drive، وكذلك تحديد المستند المطلوب للعرض والتحرير.
لكن هذا هو موضوع المقال التالي.

PS كود التطبيق التجريبي قبل تنفيذ الآلية Drag & Dropوبعدها على جيثب .


Source: https://habr.com/ru/post/ar425817/


All Articles