Seret & Jatuhkan di aplikasi iOS Anda



Mekanisme Drag & Drop yang berjalan di iOS 11 dan iOS 12 adalah cara untuk menyalin atau memindahkan data secara asinkronik dalam satu aplikasi dan antar aplikasi yang berbeda. Meskipun teknologi ini berusia sekitar 30 tahun, teknologi ini benar-benar menjadi "terobosan" di iOS karena ketika menyeret sesuatu di iOS , multitouch memungkinkan Anda berinteraksi secara bebas dengan seluruh sistem dan mengumpulkan data untuk diatur ulang dari aplikasi yang berbeda.

iOS memungkinkan untuk menangkap beberapa elemen sekaligus. Selain itu, mereka tidak harus berada dalam aksesibilitas yang mudah untuk seleksi: Anda dapat mengambil objek pertama, lalu pergi ke aplikasi lain dan mengambil sesuatu yang lain - semua objek akan dikumpulkan dalam "tumpukan" di bawah jari Anda. Kemudian panggil dok universal di layar, buka aplikasi apa saja di sana dan tangkap objek ketiga, lalu buka layar dengan aplikasi berjalan dan, tanpa melepaskan objek, buang mereka ke salah satu program yang terbuka. Kebebasan bertindak seperti itu dimungkinkan di iPad , di iPhone , cakupan Drag & Drop di iOS terbatas pada kerangka kerja satu aplikasi.

Aplikasi paling populer ( Safary , Chrome , IbisPaint X , Mail , Photos , Files , dll.) Sudah memiliki mekanisme Drag & Drop . Selain itu, Apple memberi para pengembang API sangat sederhana dan intuitif untuk menyematkan mekanisme Drag & Drop di aplikasi Anda. Mekanisme Drag & Drop , sama seperti gerakan, bekerja di UIView dan menggunakan konsep Interaksi , sedikit seperti gerakan, sehingga Anda dapat memikirkan mekanisme Drag & Drop seperti gerakan yang sangat kuat.

Itu, serta gerakan, sangat mudah untuk diintegrasikan ke dalam aplikasi Anda. Terutama jika aplikasi Anda menggunakan tabel UITableView atau koleksi UICollectionView , karena bagi mereka API Drag & Drop ditingkatkan dan dinaikkan ke tingkat abstraksi yang lebih tinggi dalam arti bahwa Collection View itu sendiri membantu Anda dengan indexPath dari elemen koleksi yang ingin Anda seret dan jatuhkan Drag . Dia tahu di mana jari Anda berada dan mengartikannya sebagai indexPath dari elemen koleksi yang Anda "seret" Drag saat ini, atau sebagai indexPath dari elemen koleksi di mana Anda "menjatuhkan" Drop sesuatu. Jadi Collection View menyediakan Anda dengan indexPath , tetapi sebaliknya itu benar-benar API Drag & Drop sama seperti UIView biasa.

Proses Drag & Drop di iOS memiliki 4 fase berbeda:

Angkat


Angkat (lifting) - ini adalah saat pengguna melakukan gerakan tekan lama , yang menunjukkan elemen yang akan "seret dan lepas". Pada saat ini, yang disebut " lift preview " yang sangat ringan dari elemen yang ditunjukkan terbentuk, dan kemudian pengguna mulai menyeret jari-jarinya.



Seret (seret dan lepas)


Seret (seret dan lepas) - ini adalah saat pengguna memindahkan objek di permukaan layar. Selama fase ini, " lift preview " untuk objek ini dapat dimodifikasi (tanda tambah "+" berwarna hijau atau tanda lainnya muncul) ...



... beberapa interaksi dengan sistem juga diperbolehkan: Anda dapat mengklik pada beberapa objek lain dan menambahkannya ke sesi "seret dan lepas" saat ini:



Jatuhkan


Drop terjadi ketika pengguna mengangkat jari. Pada saat ini, dua hal dapat terjadi: objek Drag akan dihancurkan, atau objek Drop akan "jatuh" di tujuan.



Transfer Data


Jika proses Seret " seret dan lepas" tidak dibatalkan dan setel " Drop " terjadi, maka Transfer Data (transfer data) terjadi, saat "titik drop" meminta data dari "sumber", dan transfer data asinkron terjadi.

Dalam tutorial ini, kami akan menunjukkan kepada Anda cara mengintegrasikan mekanisme Drag & Drop ke dalam aplikasi iOS Anda dengan mudah menggunakan aplikasi demo Galeri Gambar, yang diambil dari kursus pekerjaan rumah Stanford CS193P .
Kami akan menganugerahkan Collection View kemampuan untuk mengisi diri kita sendiri dengan gambar-gambar DI LUAR, serta mengatur kembali elemen-elemen DALAM dengan menggunakan mekanisme Drag & Drop . Selain itu, mekanisme ini akan digunakan untuk membuang elemen yang tidak perlu dari Collection View ke dalam "tong sampah", yang merupakan UIView normal dan diwakili oleh tombol pada panel navigasi. Kami juga dapat berbagi gambar yang dikumpulkan di Galeri kami dengan aplikasi lain menggunakan mekanisme Drag & Drop , misalnya, Notes atau Notes atau Mail atau perpustakaan foto ( Photo ).

Tetapi sebelum fokus pada implementasi mekanisme Drag & Drop dalam aplikasi demo "Galeri Gambar", saya akan membahas komponen utamanya secara singkat.

Fitur dari aplikasi demo "Galeri Gambar"


Antarmuka pengguna ( UI ) aplikasi Galeri Gambar sangat sederhana. Ini adalah "cuplikan layar" dari Image Gallery Collection View Controller dimasukkan ke Navigation Controller :



Bagian utama dari aplikasi tentu saja adalah Image Gallery Collection View Controller , yang didukung oleh kelas ImageGalleryCollectionViewController dengan Model Galeri Gambar sebagai variabel var imageGallery = ImageGallery () :



Model diwakili oleh struktur ImageGallery struct yang berisi larik gambar gambar , di mana setiap gambar dijelaskan oleh struktur ImageModel struct yang berisi URL url lokasi gambar (kami tidak akan menyimpan gambar itu sendiri) dan rasio aspeknya:



ImageGalleryCollectionViewController kami mengimplementasikan protokol DataSource :



Sel khusus dalam koleksi sel berisi gambar imageView: UIImageView! dan indikator aktivitas pemintal: UIActivityIndicatorView! dan didukung oleh subclass kustom ImageCollectionViewCell dari kelas UICollectionViewCell :



Public API dari kelas ImageCollectionViewCell adalah URL gambar imageURL . Segera setelah kami menginstalnya, UI kami diperbarui, yaitu, data untuk gambar dipilih secara tidak sinkron pada gambar iniURL dan ditampilkan dalam sel. Ketika data sedang diambil dari jaringan, indikator aktivitas pemintal bekerja, menunjukkan bahwa kita sedang dalam proses mengambil data.

Saya menggunakan antrian global (qos: .userInitiated) dengan argumen kualitas layanan qos untuk mendapatkan data pada URL diberikan, yang diatur ke .userInitiated , karena saya memilih data berdasarkan permintaan pengguna:



Setiap kali Anda menggunakan variabel Anda sendiri di dalam penutupan, dalam kasus kami itu adalah imageView dan imageURL , kompiler memaksa Anda untuk menempatkan diri di depan mereka . sehingga Anda bertanya pada diri sendiri: "Apakah ada" tautan siklik memori "?" Kami tidak memiliki " memory cycle " eksplisit di sini, karena diri sendiri tidak memiliki pointer ke penutupan ini.

Namun, dalam kasus multithreading, Anda harus ingat bahwa sel - sel dalam Collection View dapat digunakan kembali berkat metode dequeueReusableCell . Setiap kali sel (baru atau digunakan kembali) muncul di layar, gambar diunduh dari jaringan secara tidak sinkron (saat ini " pemintal " indikator aktivitas pemintalan sedang berputar).

Segera setelah unduhan selesai dan gambar diterima, UI sel koleksi ini diperbarui. Tapi kami tidak menunggu gambar dimuat, kami terus menggulung koleksi dan sel koleksi yang kami tandai meninggalkan layar tanpa memperbarui UI kami. Namun, gambar baru akan muncul di bawah dan sel yang sama yang telah meninggalkan layar akan digunakan kembali, tetapi untuk gambar lain, yang dapat dengan cepat memuat dan memperbarui UI . Pada saat ini, pemuatan gambar yang dimulai sebelumnya dalam sel ini akan kembali dan layar akan diperbarui, yang akan mengakibatkan hasil yang salah. Ini karena kami menjalankan berbagai hal yang bekerja dengan jaringan di utas yang berbeda. Mereka kembali pada waktu yang berbeda.

Bagaimana kita dapat memperbaiki situasi?
Dalam kerangka mekanisme GCD yang kami gunakan, kami tidak dapat membatalkan unduhan gambar sel yang meninggalkan layar, tetapi kami dapat, ketika data imageData kami berasal dari jaringan, periksa url URL yang menyebabkan pemuatan data ini dan bandingkan dengan yang diinginkan pengguna. sel ini saat ini, mis. imageURL . Jika tidak cocok, maka kami tidak akan memperbarui sel UI dan menunggu data gambar yang kami butuhkan:



Baris kode url yang tampaknya tidak masuk akal ini == self.imageURL membuat semuanya berfungsi dengan benar di lingkungan multi-utas yang memerlukan imajinasi non-standar. Faktanya adalah bahwa beberapa hal dalam pemrograman multi-threaded terjadi dalam urutan yang berbeda dari kode yang ditulis.

Jika tidak mungkin untuk memilih data gambar, gambar dihasilkan dengan pesan kesalahan dalam bentuk string "Kesalahan" dan emoji dengan "kerutan". Hanya ruang kosong di Collection View kami yang dapat sedikit membingungkan pengguna:



Kami tidak ingin gambar dengan pesan kesalahan mengulangi aspekRasio gambar kesalahan ini, karena dalam hal ini teks bersama dengan emoji akan ditarik atau dikompresi. Kami ingin itu menjadi netral - kotak, yaitu, ia akan memiliki aspek rasio aspek mendekati 1,0.



Kami harus memberi tahu Controller kami tentang keinginan ini sehingga ia mengoreksi aspek rasio aspek untuk indexPath yang sesuai di Model imageGallery- nya. Ini adalah tugas yang menarik, ada banyak cara untuk menyelesaikannya, dan kami akan memilih yang termudah dari mereka - menggunakan Opsional penutupan var closAspectRatio: (() -> Void)? . Dapat menyamai nol dan tidak perlu diinstal jika ini tidak perlu:



Saat memanggil closure changeAspectRatio? () Dalam kasus pengambilan data yang salah, saya menggunakan rantai opsional . Sekarang siapa pun yang tertarik pada beberapa jenis pengaturan saat menerima gambar yang salah dapat mengatur penutupan ini untuk sesuatu yang spesifik. Dan inilah yang kami lakukan di Controller kami dalam metode cellForItemAt :



Detail dapat ditemukan di sini .

Untuk menampilkan gambar dengan aspekRasio yang benar, metode sizeForItemAt dari delegasi UICollectionViewDelegateFlowLayout digunakan :



Selain Collection View gambar Collection View , di UI kami UI kami menempatkan Bar Button di panel navigasi dengan gambar GarbageView khusus yang berisi "tong sampah" sebagai subview :



Pada gambar ini, warna latar belakang untuk GarbageView sendiri dan tombol UIButton dengan gambar "tempat sampah" (sebenarnya ada latar belakang transparan) secara khusus diubah sehingga Anda melihat bahwa pengguna yang "membuang" gambar Galeri ke "tempat sampah" lebih banyak ruang untuk bermanuver ketika "menjatuhkan" Drop daripada hanya ikon tempat sampah.
Kelas GarbageView memiliki dua inisialisasi dan keduanya menggunakan metode setup () :



Dalam metode setup () , saya juga menambahkan myButton sebagai subview dengan gambar "tong sampah" yang diambil dari Bar Button standar Bar Button :



Saya menetapkan latar belakang transparan untuk GarbageView :



Ukuran tong sampah dan posisinya akan ditentukan dalam metode layoutSubviews () dari kelas UIView , tergantung pada batasan UIView yang diberikan:



Ini adalah versi awal dari aplikasi demo "Galeri Gambar", ini terletak di Github di folder ImageGallery_beginning . Jika Anda menjalankan versi aplikasi "Galeri Gambar" ini, Anda akan melihat hasil aplikasi yang bekerja pada data uji, yang kemudian akan kami hapus dan akan mengisi "Galeri Gambar" secara eksklusif di luar:



Rencana untuk menerapkan mekanisme Drag & Drop di aplikasi kami adalah sebagai berikut:

  1. pertama, kita akan memberkahi Collection View kita Collection View kemampuan untuk "menyeret" Drag DARI itu gambar UIImage baik secara eksternal maupun lokal,
  2. maka kami akan mengajarkan koleksi gambar Collection View untuk menerima "seret" Drag eksternal atau lokal dari UIImage ,
  3. kami juga akan mengajarkan GarbageView kami dengan tombol tempat sampah untuk menerima gambar UIImage yang diseret dari Collection View lokal dan menghapusnya dari Collection View


Jika Anda pergi ke akhir tutorial ini dan menyelesaikan semua perubahan kode yang diperlukan, Anda akan menerima versi final dari aplikasi demo "Galeri Gambar", di mana mekanisme Drag & Drop telah dilaksanakan. Itu terletak di Github di folder ImageGallery_finished .

Kinerja mekanisme Drag & Drop di Collection View Anda disediakan oleh dua delegasi baru.
Metode delegasi pertama, dragDelegate , dikonfigurasikan untuk menginisialisasi dan mengkustomisasi Drag dan Drop Drags .
Metode delegasi kedua, dropDelegate , selesaikan drag dan drop Drags dan, pada dasarnya, memberikan pengaturan Data transfer data dan animasi khusus ketika Drop diatur ulang, serta hal-hal serupa lainnya.

Penting untuk dicatat bahwa kedua protokol ini sepenuhnya independen. Anda dapat menggunakan satu atau protokol lain jika Anda hanya perlu "menyeret" Drag atau hanya "menjatuhkan" Drop , tetapi Anda dapat menggunakan kedua protokol sekaligus dan Drag dan jatuhkan Drag dan "jatuhkan" Drop , yang membuka fungsionalitas tambahan Drag & Drop mekanisme untuk menyusun ulang item di Collection View Anda.

Seret dan Jatuhkan Elemen Drag DARI Collection View


Menerapkan protokol Drag sangat sederhana, dan hal pertama yang harus selalu Anda lakukan adalah mengatur diri sendiri sebagai delegasi dragDelegate :



Dan, tentu saja, di bagian paling atas kelas ImageGalleryCollectionViewController, Anda harus mengatakan "Ya," kami menerapkan protokol UICollectionViewDragDelegate :



Segera setelah kami melakukan ini, kompiler mulai “mengeluh”, kami klik pada lingkaran merah dan kami ditanya: “Apakah Anda ingin menambahkan metode yang diperlukan dari protokol UICollectionViewDragDelegate ?”
Saya menjawab: "Tentu saja saya mau!" dan klik tombol Fix :



Satu-satunya metode yang diperlukan dari protokol UICollectionViewDragDelegate adalah metode itemsForBeginning , yang akan memberi tahu sistem Drag kita “drag and drop”. Metode itemsForBeginning dipanggil ketika pengguna mulai "menyeret" ( Dragging ) sel di dalam sel koleksi.

Perhatikan bahwa dalam metode ini, Collection View telah menambahkan indexPath . Ini akan memberi tahu kita elemen koleksi mana, indexPath mana, yang akan kita “drag and drop”. Ini benar-benar sangat nyaman bagi kami, karena itu adalah aplikasi yang bertanggung jawab untuk menggunakan argumen sesi dan indexPath untuk mencari tahu bagaimana menangani drag dan drop Drag .

Jika array [UIDragItems] dari elemen " draggable " dikembalikan, "drag" dari Drag diinisialisasi, jika array kosong [] dikembalikan, "drag" dari Drag diabaikan.

Saya akan membuat fungsi dragItems (at: indexPath) pribadi kecil dengan argumen indexPath . Ia mengembalikan array [UIDragItem] yang kita butuhkan .



Seperti apa tampilan UIDragItem drag-and-drop?
Dia hanya memiliki satu hal yang sangat PENTING yang disebut itemProvider . itemProvider hanyalah sesuatu yang dapat menyediakan data yang akan diseret.

Dan Anda berhak bertanya: "Bagaimana dengan" menyeret dan menjatuhkan "elemen UIDragItem yang tidak memiliki data?" Item yang ingin Anda seret mungkin tidak memiliki data, misalnya, karena membuat data ini adalah operasi yang mahal. Ini mungkin gambar gambar atau sesuatu yang memerlukan pengunduhan data dari Internet. Hebatnya adalah bahwa operasi Drag & Drop benar-benar tidak sinkron. Saat Anda mulai menyeret dan menjatuhkan Drag , itu benar-benar objek yang sangat ringan ( lift preview ), Anda seret ke mana-mana dan tidak ada yang terjadi selama "seret" ini. Tapi begitu Anda "menjatuhkan" Drop suatu tempat objek Anda, kemudian, menjadi itemProvider , itu benar-benar harus menyediakan objek "diseret" dan "dilempar" dengan data nyata, bahkan jika itu membutuhkan waktu tertentu.

Untungnya, ada banyak itemProvider bawaan . Ini adalah kelas yang sudah ada di iOS dan yang merupakan itemPoviders , seperti, misalnya, NSString , yang memungkinkan Anda untuk menarik dan melepas teks tanpa font. Tentu saja, ini adalah gambar UIImage . Anda dapat memilih dan seret dan letakkan seluruh gambar UIImages . Kelas NSURL , yang benar-benar luar biasa. Anda dapat pergi ke halaman Web , pilih URL dan “jatuhkan” di mana pun Anda inginkan. Ini bisa berupa tautan ke artikel atau URL untuk gambar, karena akan ada di demo kami. Ini adalah kelas warna dari UIColor , elemen peta MKMapItem , kontak CNC dari buku alamat, Anda dapat memilih dan menyeret banyak hal. Semuanya adalah itemProviders .

Kita akan "drag and drop" gambar UIImage . Itu terletak di sel sel Collection View dengan indexPath , yang membantu saya memilih sel sel , dapatkan Outlet imageView dari sana dan dapatkan gambar gambarnya .

Mari ungkapkan ide ini dengan beberapa baris kode.
Pertama, saya meminta Collection View saya tentang sel untuk elemen item yang terkait dengan indexPath ini.



Metode cellForItem (at: IndexPath) untuk Collection View hanya berfungsi untuk sel yang terlihat, tetapi, tentu saja, itu akan berfungsi dalam kasus kami, karena saya “drag and drop” elemen koleksi Drag di layar dan itu terlihat.

Jadi, saya mendapat sel "yang bisa diseret".
Selanjutnya, saya menggunakan operator as? ke sel ini sehingga memiliki JENIS subclass kustom saya. Dan jika ini berhasil, maka saya mendapatkan Outlet imageView , dari mana saya mengambil gambar gambarnya . Saya baru saja "menangkap" gambar gambar untuk indexPath ini.

Sekarang saya memiliki gambar gambar , yang harus saya lakukan adalah membuat salah satu UIDragItems ini menggunakan gambar gambar yang dihasilkan sebagai itemProvider , yaitu hal yang menyediakan data kepada kami.
Saya bisa membuat dragItem menggunakan konstruktor UIDragItem , yang mengambil itemProvider sebagai argumen:



Kemudian kami membuat itemProvider untuk gambar gambar juga menggunakan konstruktor NSItemProvider . Ada beberapa konstruktor untuk NSItemProvider , tetapi di antara mereka ada satu yang sangat bagus - NSItemProvider (objek: NSItemProviderWriting) :



Anda cukup memberikan objek objek ke konstruktor NSItemProvider ini, dan ia tahu cara membuat itemProvider darinya. Sebagai objek seperti itu , saya memberikan gambar gambar gambar yang saya terima dari sel sel dan mendapatkan itemProvider untuk UIImage .
Dan itu saja. Kami membuat dragItem dan mengembalikannya sebagai array yang memiliki satu elemen.

Tapi sebelum aku kembali dragItem , aku akan melakukan satu hal lagi, yaitu, untuk mengatur variabel localObject untuk dragItem , sama dengan gambar yang dihasilkan gambar .



Apa artinya ini?
Jika Anda melakukan "seret dan lepas" Dragsecara lokal, yaitu di dalam aplikasi Anda, maka Anda tidak perlu melewati semua kode ini yang terkait dengan itemProvider , melalui pengambilan data yang tidak sinkron. Anda tidak perlu melakukan ini, Anda hanya perlu mengambil localObject dan menggunakannya. Ini adalah semacam "korsleting" dengan "drag and drop" lokal Drag.

Kode yang kami tulis akan berfungsi ketika "menyeret" di Dragluar koleksi kami Collection Viewke aplikasi lain, tetapi jika kami "menyeret" Dragsecara lokal, kami dapat menggunakan localObject . Selanjutnya, saya mengembalikan array dari satu elemen dragItem .

Omong-omong, jika karena alasan tertentu saya tidak bisa mendapatkan gambar untuk sel sel ini , maka saya mengembalikan array kosong [] , ini berarti bahwa "seret dan lepas" Dragdibatalkan.



Selain objek lokal localObject , Anda dapat mengingat konteks lokal localContext untuk kami Dragsesi sesi . Dalam kasus kami ini akan menjadi koleksi CollectionView dan hal ini berguna untuk kami setelah itu:



Memiliki "drag and drop" Drag, Anda dapat menambahkan item item ini "drag and drop", hanya melakukan gerakan tap pada mereka. Hasilnya, Anda dapat menarikDragbanyak item sekaligus. Dan ini mudah diimplementasikan dengan metode delegasi lain , UICollectionViewDragDelegate , sangat mirip dengan metode itemsForeginning , metode bernama itemsForAddingTo . Metode itemsForAddingTo terlihat persis sama dengan metode itemsForeginning dan mengembalikan hal yang sama persis, karena itu juga memberi kita indexPath tentang apa yang "direkam" oleh pengguna selama proses "seret Dragdan lepas" , dan saya hanya perlu mendapatkan gambar gambar dari sel ke yang "direkam" oleh pengguna, dan mengembalikannya.



Kembalikan array kosong [] dari metode itemsForAddingTomengarah pada fakta bahwa gerakan tap akan ditafsirkan dengan cara biasa, yaitu, sebagai pilihan sel sel ini .
Dan hanya itu yang kita butuhkan untuk drag and drop Drag.
Kami meluncurkan aplikasi.
Saya memilih gambar "Venice", tahan sebentar dan mulai bergerak ...



... dan kita benar-benar dapat menyeret gambar ini ke dalam aplikasi Photos, seperti yang Anda lihat tanda tambah hijau "+" di sudut kiri atas gambar "draggable". Saya dapat melakukan gerakan tap pada gambar Artika lain dari koleksi Collection View...



... dan sekarang kita dapat memasukkan dua gambar ke dalam aplikasi Photos:



Karena Photosmekanisme sudah ada di dalam aplikasiDrag & Dropmaka semuanya bekerja dengan baik dan itu keren.
Jadi, "menyeret" Dragdan "membuang" Dropgambar Galeri ke aplikasi lain berfungsi untuk saya , saya tidak perlu berbuat banyak dalam aplikasi saya, kecuali untuk memberikan gambar gambar sebagai array [UIDragItem] . Ini adalah salah satu dari banyak fitur hebat mekanisme Drag & Drop- sangat mudah untuk membuatnya bekerja di kedua arah.

Setel Ulang DropGambar Ke KoleksiCollection View


Sekarang kita perlu membuat Dropbagian untuk koleksi saya Collection Viewsehingga kita dapat "membuang Drop" gambar "yang diseret" di dalam koleksi ini. Gambar "dapat diseret" dapat "datang" dari LUAR, atau langsung DI DALAM koleksi ini.
Untuk melakukan ini, kita melakukan hal yang sama dilakukan untuk mendelegasikan dragDelegate , yaitu membuat diri kita, diri , mendelegasikan dropDelegate dalam metode viewDidLoad :



Sekali lagi, kita harus mendaki ke puncak kelas kami ImageGalleryCollectionViewController dan memverifikasi protokol implementasi UICollectionViewDropDelegate :



Segera setelah kami menambahkan protokol baru kami, kompiler lagi mulai "mengeluh" bahwa kami tidak mengimplementasikan protokol ini. Kami mengklik tombol Fixdan metode yang diperlukan dari protokol ini muncul di depan kami. Dalam hal ini, kami diberitahu bahwa kami harus menerapkan metode performDrop :



Kami harus melakukan ini, jika tidak “reset” tidak akan terjadi Drop. Sebenarnya, saya akan mengimplementasikan metode performDrop terakhir, karena ada beberapa Applemetode lain yang sangat direkomendasikan yang perlu Anda terapkan untuk Dropbagian itu. Ini adalah canHandle dan dropSessionDidUpdate :



Jika kita menerapkan dua metode ini, maka kita bisa mendapatkan tanda tambah hijau kecil "+" ketika kita menyeret gambar dari LUAR ke koleksi kami ollection View, dan di samping itu, mereka tidak akan mencoba untuk membuang apa yang tidak kita mengerti.

Mari kita menerapkan canHandle . Kita memiliki versi Anda dari metode canHandle , yang dimaksudkan untuk pengumpulan ollection View, tetapi metode ini ollection Viewterlihat persis sama dengan metode serupa untuk UIView biasa , tidak ada indexPath di sana , kami hanya perlu mengembalikan session.canLoadObjects (ofClass: UIImage.self) , dan itu berarti bahwa saya menerima "reset" objek dari cl ini PAS dalam koleksi saya ollection View:



Tapi ini tidak cukup untuk "membuang" Dropgambar ke koleksi saya Collection ViewDI LUAR.
Jika "reset" Dropgambar berlangsung dalam koleksi Collection View, pengguna akan mereorganisasi unsur sendiri item melalui mekanisme Drag & Drop, maka hanya satu gambar UIImage , dan penerapan metode canHandle akan terlihat seperti dengan cara di atas.

Tetapi jika "dumping" Dropgambar terjadi DI LUAR, maka kita hanya boleh menangani "drag and drop" Dragyang merupakan gambar UIImage bersama dengan URLgambar ini, karena kita tidak akan menyimpan gambar UIImage secara langsungdalam Model. Dalam hal ini, saya hanya akan mengembalikan true dalam metode canHandle jika beberapa kondisi terpenuhi pada saat yang sama : session.canLoadObjects (ofClass: NSURL.self) && session.canLoadObjects (ofClass: UIImage.self) :



Saya harus menentukan apakah saya berurusan dengan "reset" DI LUAR ATAU DALAM. Saya akan melakukan ini dengan menggunakan konstanta isSelf yang dihitung , untuk perhitungan yang saya dapat menggunakan hal itu dalam Dropsesi sesi sebagai Dragsesi lokal localDragSession . Dalam lokal ini Dragsesi, pada gilirannya, adalah konteks lokal localContext .
Jika Anda ingat, kami menetapkan konteks lokal ini dalam metode iniitemsForVeginning Drag delegasi UICollectionViewDragDelegate :



Aku akan menjelajahi konteks lokal localContext untuk kesetaraan dalam koleksi saya CollectionView . Benar, TYPE localContext akan menjadi Any , dan saya perlu melakukan casting TYPE Any menggunakan operator as? UICollectionView :



Jika konteks lokal (session.localDragSession .localContext sebagai UICollectionView ??) Apakah koleksi saya CollectionView , variabel diperluas isSelf adalah benardan ada "reset" lokal DI DALAM koleksi saya. Jika persamaan ini dilanggar, maka kita berurusan dengan "reset" DropDI LUAR.

Metode canHandle melaporkan bahwa kami hanya dapat menangani "seret dan lepas" Dragke koleksi kami Collection View. Kalau tidak, lebih jauh, tidak masuk akal untuk berbicara tentang "dumping Drop. "

Jika kita terus "ulang" Drop, masih sampai saat ini bahwa pengguna akan mengangkat jari Anda dari layar dan akan ada nyata "ulang" Drop, kita harus melaporkan iOSmenggunakan metode dropSessionDidUpdate delegasi UICollectionViewDropDelegate tentang tawaran kami UIDropProposal untuk melaksanakan ulang Drop.

Dalam metode ini, kita harus mengembalikan Dropkalimat yang dapat memiliki nilai .copy atau .move atau .cancel atau .forbidden untuk argumen operasi . Dan ini semua kemungkinan yang kita miliki dalam kasus biasa ketika berhadapan dengan UIView biasa .

Tetapi koleksi Collection Viewberjalan lebih jauh dan menawarkan untuk mengembalikan penawaran khusus UICollectionViewDropProposal , yang merupakan subclasskelas dari UIDropProposal dan memungkinkan Anda untuk menentukan, selain operasi operasi, parameter maksud tambahan untuk koleksi Collection View.

Parameterintent memberi tahu koleksiCollection Viewapakah kita ingin elemen "dibuang" ditempatkan di dalam sel yang ada , atau apakah kita ingin menambahkan sel baru . Lihat perbedaannya? Dalam hal pengumpulan,Collection Viewkita harus menyampaikan maksud niat kita .

Dalam kasus kami, kami selalu ingin menambahkan sel baru, jadi Anda akan melihat seperti apa parameter maksud kami.
Kami memilih konstruktor kedua untuk UICollectionViewDropProposal :



Dalam kasus kami, kami selalu ingin menambahkan sel baru dan parameter maksud akan mengambil nilai .insertAtDestinationIndexPath sebagai lawan.insertIntoDestinationIndexPath .



Saya kembali menggunakan konstanta yang dihitung itu sendiri , dan jika itu adalahreorganisasi sendiri , maka saya akan pindah. Pindah , kalau tidak saya menyalin .copy . Dalam kedua kasus, kami menggunakan .insertAtDestinationIndexPath , yaitu memasukkan sel baru.

Sejauh ini saya belum menerapkan metode performDrop , tetapi mari kita lihat apa yang sudah dapat dilakukan koleksiCollection Viewdengan sepotong kecil informasi yang kami sediakan untuk itu.

Saya seret gambar dariSafarimesin pencariGoogle, dan tanda "+" berwarna hijau muncul di atas gambar ini, menunjukkan bahwa Galeri Gambar kami tidak hanya siap untuk menerima dan menyalin gambar ini bersamanya URL, tetapi juga menyediakan tempat di dalam koleksi Collection View:



saya dapat mengklik sepasang gambar lain di Safari, dan Sudah akan ada 3 gambar "diseret":



Tetapi jika saya mengangkat jari saya dan "menjatuhkan" Dropgambar-gambar ini, mereka tidak akan ditempatkan di Galeri kami, tetapi hanya kembali ke tempat sebelumnya, karena kami belum menerapkan metode performDrop .



Anda bisa melihat bahwa koleksinya Collection Viewsudah tahu apa yang ingin saya lakukan.
Koleksinya Collection Viewbenar-benar luar biasa untuk mekanisme ini.Drag & Drop, dia memiliki fungsionalitas yang sangat kuat untuk ini. Kami nyaris tidak menyentuhnya dengan menulis 4 baris kode, dan dia sudah bergerak cukup jauh dalam persepsi "reset" Drop.
Mari kita kembali ke kode dan mengimplementasikan metode performDrop .



Dalam metode ini, kita tidak akan dapat bertahan dengan 4 baris kode, karena metode performDrop sedikit lebih rumit, tetapi tidak terlalu banyak.
Ketika "reset" terjadi Drop, dalam metode performDrop kita perlu memperbarui Model kita, yang merupakan galeri gambar imageGallery dengan daftar gambar gambar , dan kita perlu memperbarui koleksi koleksi visual kita .

Kami memiliki dua skenario "reset" yang berbeda Drop.

Jika ada "reset" Dropdari koleksi collectionView saya , maka saya harus "mengatur ulang" Dropelemen koleksi di tempat baru dan menghapusnya dari tempat lama, karena dalam hal ini saya memindahkan ( .move ) elemen koleksi ini. Ini adalah tugas yang sepele.

Ada "reset" Dropdari aplikasi lain, maka kita harus menggunakan properti itemProvider dari elemen item "diseret" untuk memilih data.

Saat kami melakukan "reset" Dropdi koleksi collectionView , koleksi memberi kami koordinator koordinator. Pertama dan terpenting, kami melaporkan koordinator koordinator , itu destinationIndexPath , yaitu indexPath "-destination", "ulang" Drop, itu adalah di mana kita akan "ulang."



Tapi destinationIndexPath mungkin nihil , karena Anda dapat menyeret gambar "dibuang" ke bagian koleksi Collection Viewyang bukan tempat di antara beberapa sel yang ada , sehingga bisa jadi nihil . Jika situasi ini terjadi, maka saya membuat IndexPath dengan elemen item ke-0 di bagian bagian ke-0 .



Saya bisa memilih indexPath lainnya , tetapi saya akan menggunakan indexPath ini secara default.

Sekarang kita tahu di mana kita akan melakukan "reset" Drop. Kita harus melalui semua koordinator "reset". Item yang disediakan oleh koordinator . Setiap item dari daftar ini memiliki TYPE UICollectionViewDropItem dan dapat memberikan kami informasi yang sangat menarik.

Sebagai contoh, jika saya bisa mendapatkan sourceIndexPath dari item.sourceIndexPath , saya akan tahu persis apa itu "drag and drop" Dragdilakukan dengan sendirinya, diri, dan sumber seret Dragadalah item koleksi dengan indexPath sama dengan sourceIndexPath :



Saya bahkan tidak perlu melihat localContext dalam hal ini untuk mengetahui bahwa "seret dan lepas" ini dilakukan DI DALAM koleksi collectionView . Wow!

Sekarang saya tahu sumber sourceIndexPath dan "tujuan" destinationIndexPath Drag & Drop , dan tugas menjadi sepele. Yang perlu saya lakukan adalah memperbarui Model sehingga sumber dan "tujuan" ditukar, dan kemudian memperbarui koleksi collectionView , di mana Anda harus menghapus item koleksi dengan sourceIndexPath dan menambahkannya ke koleksi dengan destinationIndexPath .

Kasus lokal kami adalah yang paling sederhana, karena dalam kasus ini mekanismenya Drag & Dropbekerja tidak hanya dalam aplikasi yang sama, tetapi dalam koleksi collectionView yang sama , dan saya bisa mendapatkan semua informasi yang diperlukan menggunakan koordinator koordinator. Mari kita implementasikan dalam kasus lokal paling sederhana ini:



Dalam kasus kami, saya bahkan tidak perlu localObject , yang saya "sembunyikan" sebelumnya ketika saya membuat dragItem dan yang sekarang saya pinjam dari item "diseret" dalam koleksi item dalam item form.localObject . Kita akan membutuhkannya saat "membuang" Dropgambar ke "tempat sampah," yang ada di aplikasi yang sama, tetapi bukan koleksi collectionView yang sama . Dua IndexPathes sudah cukup bagi saya sekarang : sumber sourceIndexPath dan "tujuan" destinationIndexPath .

Saya mendapat informasi terlebih dahuluimageInfo tentang gambar di tempat lama dari Model, menghapusnya dari sana. Dan kemudian masukkan ke array gambar model saya imageGallery informasi imageInfo gambar dengan indeks baru destinationIndexPath.item . Ini adalah bagaimana saya memperbarui Model saya:



Sekarang saya harus memperbarui koleksi collectionView itu sendiri. Sangat penting untuk memahami bahwa saya tidak ingin membebani semua data di collectionViewView sayadengan reloadData () di tengah proses "seret dan lepas"Drag, karena ini menginstal ulang seluruh "Dunia" Galeri Gambar kami, yang sangat buruk, JANGAN LAKUKAN. Sebagai gantinya, saya akan membereskan dan memasukkan elemenitem secara individual:



Saya menghapus item koleksi collectionView dengan sourceIndexPath dan memasukkan item koleksi baru dengan destinationIndexPath .

Sepertinya kode ini berfungsi dengan baik, tetapi dalam kenyataannya, kode ini dapat "crash" aplikasi Anda. Alasannya adalah bahwa Anda membuat banyak perubahan pada koleksi collectionView Anda , dan dalam hal ini setiap langkah mengubah koleksi perlu disinkronkan dengan Model secara normal, yang tidak diamati dalam kasus kami, karena kami melakukan kedua operasi pada saat yang sama: hapus dan sisipkan. Oleh karena itu koleksi collectionViewakan berada di beberapa titik dalam kondisi TIDAK disinkronkan dengan Model.

Tapi ada yang benar-benar keren cara untuk berkeliling itu, yaitu bahwa koleksi CollectionView memiliki metode bernama performBatchUpdates , yang memiliki sirkuit ( closure) dan dalam rangkaian ini saya dapat menempatkan sejumlah tersebut deleteItems , insertItems , moveItems dan semua yang saya inginkan:



sekarang deleteItems dan insertItems akan dilakukan sebagai operasi tunggal, dan tidak akan pernah melihat kurangnya sinkronisasi model Anda dengan koleksi CollectionView .

Dan akhirnya, hal terakhir yang perlu kita lakukan adalah meminta koordinator untuk mengimplementasikan dan menghidupkan "reset" itu sendiri Drop:



Segera setelah Anda mengangkat jari Anda dari layar, gambar bergerak, semuanya terjadi pada saat yang sama: "reset", gambar menghilang di satu tempat dan penampilan di tempat lain.
Mari kita coba untuk memindahkan gambar uji "Venesia" di Galeri Gambar kami ke akhir baris pertama ...



... dan "reset" itu:



Seperti yang kita inginkan, itu ditempatkan di akhir baris pertama.
Hore!Semuanya bekerja!

Sekarang kita TIDAK akan berurusan dengan case lokal, yaitu, ketika elemen "reset" keluar, yaitu dari aplikasi lain.
Untuk melakukan ini, kami menulis lagi dalam kode sehubungan dengan sourceIndexPath . Jika kita tidak memiliki sourceIndexPath , maka ini berarti bahwa elemen "reset" berasal dari LUAR dan kita harus menggunakan transfer data menggunakan itemProver dari item reset.dragItem.itemProvider elemen :



Jika Anda "drag Dragand drop" OUTSIDE dan "drop" ”Drop, lalu apakah informasi ini tersedia secara instan? Tidak, Anda memilih data dari hal "diseret" ASYNCHRONOUSLY. Tetapi bagaimana jika sampel membutuhkan waktu 10 detik? Apa yang akan dilakukan koleksi saat ini ollection View? Selain itu, data mungkin tidak sampai dalam urutan yang kami minta. Untuk mengelola ini tidak mudah, dan Applemengusulkan untuk ollection Viewkasus ini teknologi yang sama sekali baru untuk penggunaan pengganti Placeholders.

Anda menempatkan Collection Viewplaceholder dalam koleksi Anda Placeholder, dan koleksi Collection Viewmengatur semuanya untuk Anda, jadi yang harus Anda lakukan ketika data akhirnya dipilih adalah meminta placeholder untuk Placeholdermemanggil placeholder -nya konteks konteksdan katakan padanya bahwa Anda menerima informasi itu. Kemudian perbarui Model Anda dan konteks placeholderContext OTOMATIS bertukar sel dengan placeholder Placeholderdengan salah satu sel Anda , yang sesuai dengan jenis data yang Anda terima.

Kami melakukan semua tindakan ini dengan membuat konteks placeholderContext placeholder yang mengelola placeholder Placeholderdan yang Anda dapatkan dari koordinator koordinator , meminta Anda untuk "mengatur ulang" Dropelemen item ke placeholder Placeholder.

Saya akan menggunakan penginisialisasi untuk konteks placeholderContext placeholderbahwa “throws” dragItem ke UICollectionViewDropPlaceholder :



Objek yang akan saya “throw” Dropadalah item.dragItem , di mana item adalah untuk elemen dari loop, karena kita dapat membuang Dropbanyak koordinator.items . Kami “melempar” mereka satu per satu. Jadi item.dragItem adalah apa yang kita “seret Dragdan lepas Drop. Argumen berikutnya untuk fungsi ini adalah placeholder, dan saya akan membuatnya menggunakan initializer UICollectionViewDropPlaceholder :



Untuk melakukan ini, saya perlu tahu DIMANA saya akan memasukkan placeholderPlaceholder, mis. insertionIndexPath , serta pengidentifikasi reuseIdentifier sel yang digunakan kembali .
Argumen insertionIndexPath jelas sama dengan destinationIndexPath , itu adalah IndexPath untuk menempatkan objek "diseret", itu dihitung pada awal metode performDropWith .

Sekarang mari kita lihat id sel reuseIdentifier . Anda perlu memutuskan apa jenis sel sel merupakan tempat Anda Placeholder. Koordinator koordinator tidak memiliki sel "pra- paket " untuk pelacakPlaceholder. Bahwa Anda harus mengambil keputusan pada sel sel . Oleh karena itu, pengidentifikasi reuseIdentifiercell sel yang digunakan kembali diminta dari Anda storyboardsehingga dapat digunakan sebagai PROTOTIPE.

Saya akan menyebutnya "DropPlaceholderCell", tetapi pada dasarnya, saya bisa memberi nama apa saja.
Ini hanya string String yang akan saya gunakan pada milik saya storyboarduntuk membuat hal ini.
Kembali ke kami storyboarddan buat sel sel untuk placeholder Placeholder. Untuk melakukan ini, kita hanya perlu memilih koleksi Collection Viewdan memeriksanya. Di bidang pertama, Itemssaya berubah 1menjadi2. Ini segera membuat sel kedua bagi kami, yang merupakan salinan persis dari sel pertama.



Kami memilih sel baru kami ImageCell, mengatur pengenal " DropPlaceholderCell", menghapus semua UIelemen dari sana , termasuk Image View, karena PROTOTIPE ini digunakan ketika gambar belum tiba. Kami menambahkan indikator aktivitas baru dari Palet Objek di sana Activity Indicator, itu akan berputar, membiarkan pengguna tahu bahwa saya mengharapkan beberapa "reset" data. Juga mengubah warna latar belakang Backgrounduntuk memahami bahwa ketika "Reset" dari gambar luar bekerja sama persis sel ini sel sebagai prototipe:



Selain jenis sel baru tidak harus ImageCollectionVewCell, karena tidak akan ada gambar di dalamnya. Saya akan membuat sel ini sel TYPE UIollectionCiewCell normal , karena kita tidak memerlukan apa pun Outletsuntuk kontrol:



Mari kita mengkonfigurasi indikator aktivitas Activity Indicatorsehingga mulai menjiwai dari awal, dan saya tidak perlu menulis apa pun dalam kode untuk memulainya. Untuk melakukan ini, klik pada opsi Animating:



Dan itu saja. Jadi, kami membuat semua pengaturan untuk sel ini DropPlaceholderCell, kami kembali ke kode kami. Sekarang kami memiliki pencari lokasi yang hebat yang Placeholdersiap berangkat .

Yang harus kita lakukan adalah mendapatkan data, dan ketika data diterima, kita cukup memberi tahu placeholderContext tentang konteks ini dan itu akan menukar placeholderPlaceholderdan sel "asli" kami dengan data, dan kami akan membuat perubahan pada Model.

Saya akan "memuat" SATU objek, yang akan menjadi item saya menggunakan metode loadObject (ofClass: UIImage.self) (tunggal). Saya menggunakan kode item.dragItem.itemProvider pemasok itemProvider , yang akan memberikan data elemen memiliki item yang asynchronous. Jelas bahwa jika iitemProvider terhubung , maka kita mendapatkan objek "reset" iitem di luar aplikasi ini. Berikut ini adalah metode loadObject (ofClass: UIImage.self) (tunggal):



Penutupan khusus ini TIDAK dilakukan padamain queue. Dan, sayangnya, kami harus beralih main queuemenggunakan DispatchQueue.main.async {} untuk "menangkap" rasio aspek gambar di variabel aspekRasio lokal .

Kami benar-benar telah memperkenalkan dua variabel lokal imageurl dan aspectRatio ...



... dan akan "menangkap" mereka dengan meng-upload gambar gambar dan URL url No :



Jika dua variabel lokal imageurl dan aspectRatio tidak sama untuk nihil , kami akan meminta konteks sebuah tempat placeholderSontext menggunakan metode commitInsertionberi kami kesempatan untuk mengubah Model imageGallery kami :



Dalam ungkapan ini, kami memiliki insertionIndexPath - ini adalah indexPath untuk dimasukkan, dan kami mengubah Model imageGallery kami . Hanya itu yang perlu kita lakukan, dan metode ini secara OTOMATIS akan menggantikan placeholder Placeholderdengan sel dengan memanggil metode normal cellForItemAt .

Perhatikan bahwa insertionIndexPath bisa sangat berbeda dari destinationIndexPath . MengapaKarena pengambilan sampel data mungkin memakan waktu 10 detik, tentu saja, itu tidak mungkin, tetapi mungkin butuh 10 detik. Selama ini, Collection Viewbanyak yang bisa terjadi dalam koleksi . Dapat menambahkan sel-sel baru sel , semuanya terjadi cukup cepat.

SELALU menggunakan insertionIndexPath , dan HANYA insertionIndexPath , untuk memperbarui Model Anda.

Bagaimana kami memperbarui model kami?

Kami akan menyisipkan struktur imageModel ke dalam array imageGallery.images , terdiri dari rasio aspek dari gambar aspectRatio dan URL gambar imageURL yang dikembalikan oleh penyedia terkait kepada kami .

Ini memperbarui Model imageGallery kami , dan metode commitInsertion melakukan sisanya untuk kami. Anda tidak perlu lagi melakukan sesuatu yang ekstra, tidak ada sisipan, tidak ada penghapusan, semua ini. Dan, tentu saja, karena kita berada dalam penutupan, kita perlu menambahkan diri. .



Jika kita untuk beberapa alasan tidak bisa mendapatkan rasio aspek aspectRatio dan URLgambar imageurl dari yang sesuai dengan penyedia , kesalahan mungkin telah diterima kesalahan bukan oleh penyedia , kita harus membiarkan mereka tahu konteks placeholderContext , Anda perlu untuk menghancurkan ini pengganti Placeholder, karena kita semua sama, kita tidak bisa dapatkan data lain:



Satu hal yang perlu diingat adalah URLsbahwa mereka datang dari tempat-tempat seperti ini Google, dalam kenyataannya, mereka membutuhkan transformasi kecil untuk mendapatkan "bersih"URLuntuk gambar. Bagaimana masalah ini diselesaikan dapat dilihat pada aplikasi demo ini dalam file Utilities.swiftdi Github .
Karenanya, ketika menerima URLgambar, kami menggunakan properti imageURL dari kelas URL :



Dan itu semua yang perlu Anda lakukan untuk menerima di luar sesuatu di dalam koleksi Collection View.

Mari kita lihat dalam aksi. Kami secara bersamaan meluncurkan dalam mode multitasking aplikasi demo kami ImageGallerydan Safaridengan mesin pencari Google. Di Googlekami sedang mencari gambar pada tema "Dawn" (matahari terbit). The Safarisudah dibangunDrag & Dropmekanismenya, jadi kita dapat memilih salah satu gambar ini, tahan untuk waktu yang lama, gerakkan sedikit dan seret ke Galeri Gambar kami.



Kehadiran tanda tambah hijau "+" menunjukkan bahwa aplikasi kami siap menerima gambar pihak ketiga dan menyalinnya ke koleksi Anda di lokasi yang ditentukan oleh pengguna. Setelah kami "mengatur ulang", diperlukan waktu untuk mengunduh gambar, dan saat ini berfungsi Placeholder:



Setelah unduhan selesai, gambar "reset" ditempatkan di tempat yang tepat, dan Placeholdermenghilang:



Kami dapat terus "mengatur ulang" gambar dan menempatkannya di koleksi lebih banyak gambar:



Setelah "reset" bekerja Placeholder:



Sebagai hasilnya, Galeri Gambar kami dipenuhi dengan gambar baru:



Sekarang jelas bahwa kita mampu untuk mengambil gambar dari luar, kita tidak perlu gambar tes, dan kami akan menghapus:



kami viewDidLoad menjadi sangat sederhana: itu adalah kita lakukan kami Controller Dragdan Dropmendelegasikan dan menambahkan gesture recognizer mencubit , yang mengatur jumlah gambar per baris:



Tentu saja , kita dapat menambahkan cache untuk gambar imageCache :



Kami akan mengisi imageCache ketika "reset" Dropdalam metode performDrop ...



dan ketika mengambil dari "jaringan" di kelas ImageCollectionViewCell kustom :



Dan kita akan menggunakan cache imageCache saat memainkan selsel Galeri Gambar kami di kelas khusus ImageCollectionViewCell :



Sekarang kita mulai dengan koleksi kosong ...



... lalu "jatuhkan" gambar baru ke dalam koleksi kami ...



... gambar diunggah danPlaceholderberfungsi ...



... dan gambar muncul di tempat yang tepat:



Kami terus mengisi koleksi kami DI LUAR:



Datang memuat gambar danPlaceholdersbekerja ...



Dan gambar muncul di tempat yang tepat:



Jadi, kita dapat melakukan banyak hal dengan Galeri Gambar kami: isi di luar, mengatur ulang item DI DALAM, berbagi gambar dengan aplikasi lain niyami.
Kita hanya perlu mengajarinya cara menyingkirkan gambar yang tidak perlu dengan "mengatur ulang" gambar-gambar ituDropdi "tempat sampah" yang disajikan di bilah navigasi di sebelah kanan. Seperti yang dijelaskan di bagian "Fitur Aplikasi Demo Galeri Gambar", "tempat sampah" diwakili oleh kelas GabageView , yang diwarisi dari UIView dan kita harus mengajarkannya untuk menerima gambar dari koleksi kami ollection View.

Atur ulang Dropgambar Galeri ke tempat sampah.


Segera dari tempat itu - ke tambang. Aku akan menambah GabageView "interaksi" Interaksi dan akan UIDropInteraction , karena saya sedang mencoba untuk "reset" Dropbeberapa hal. Yang kita butuhkan untuk memberikan UIDropInteraction ini adalah delegasi delegasi , dan saya akan menugaskan diri saya sendiri , kepada delegasi delegasi ini :



Secara alami, kelas GabageView kami harus mengonfirmasi bahwa kami menerapkan protokol UIDropInteractionDelegate :



Semua yang perlu kita lakukan untuk membuatnya berfungsi Drop, ini untuk mengimplementasikan metode canHandle yang sudah kita kenal ,sessionDidUpdate dan performDrop .



Namun, tidak seperti metode serupa untuk pengumpulanCollection View, kami tidak memiliki informasi tambahan dalam bentuk indexPath tempat pembuangan.

Mari kita implementasikan metode ini.
Dalam metode canHandle akan diproses hanya "drag and drop"Drag, yang mewakili citra UIImage . Oleh karena itu, sayahanyaakan mengembalikan true jika session.canLoadObjects (ofClass: UIImage.self) :



Dalam metode canHandle ,Anda pada dasarnya hanya mengatakan bahwa jika objek "draggable" bukan gambar UIImage, kemudian lebih lanjut tidak masuk akal untuk melanjutkan "reset" Drop dan memanggil metode selanjutnya.
Jika objek "draggable" adalah gambar UIImage , maka kami akan menjalankan metode sessionDidUpdate . Yang perlu kita lakukan dalam metode ini adalah mengembalikan tawaran "reset" UIDropProposal kami Drop. Dan saya siap untuk hanya menerima objek "diseret" LOCALLY dari UIImage TYPE gambar , yang dapat "dijatuhkan" di Dropmana saja di dalam GarbageView saya . GarbageView saya tidak akan berinteraksi dengan gambar yang dibuang ke luar. Jadi saya parsing menggunakan variabel session.localDragSessionapakah ada "reset" lokal Drop, dan saya mengembalikan kalimat "reset" dalam bentuk konstruktor UIDropProposal dengan argumen operasi mengambil nilai .copy , karena SELALU LOKAL "seret dan lepas" Dragdi aplikasi saya akan berasal dari koleksi Collection View. Jika "seret Dragdan lepas" dan "reset" Dropterjadi DI LUAR, maka saya mengembalikan kalimat "reset" dalam bentuk konstruktor UIDropProposal dengan argumen operasi yang mengambil nilai .fobbiden , yaitu, "terlarang" dan kami akan menerima tanda larangan "reset" alih-alih tanda hijau .



Menyalin Gambar UIImage, kami akan mensimulasikan penurunan skalanya hingga hampir 0, dan ketika "reset" terjadi, kami akan menghapus gambar ini dari koleksi Collection View.
Untuk membuat ilusi "dumping and menghilang" gambar di "tempat sampah" untuk pengguna, kami menggunakan metode previewForDropping , baru bagi kami , yang memungkinkan kita untuk mengarahkan "dumping" Dropke tempat lain dan pada saat yang sama mengubah objek "dibuang" selama animasi:



Dalam metode ini, menggunakan penginisialisasi UIDragPreviewTarget, kami mendapatkan preView baru untuk objek target yang akan dihapus dan mengarahkannya menggunakan metode retargetedPreviewke tempat baru, ke "tempat sampah", dengan skalanya turun ke hampir nol:



Jika pengguna mengangkat jarinya, maka "reset" terjadi Drop, dan saya (seperti GarbageView ) menerima pesan performDrop . Dalam pesan performDrop, kami melakukan "reset" yang sebenarnya Drop. Sejujurnya, gambar yang dibuang ke GarbageView sendiri tidak lagi menarik bagi kami, karena kami akan membuatnya tidak terlihat, kemungkinan besar fakta penyelesaian "reset" Dropakan memberi sinyal bahwa kami menghapus gambar ini dari koleksi Collection View. Untuk mencapai ini, kita harus tahu koleksi itu sendiri dan koleksi indexPathgambar yang dibuang di dalamnya. Dari mana kita bisa mendapatkannya?

Karena proses Drag & Dropberlangsung dalam satu aplikasi, itu tersedia bagi kita semua lokal: lokal Dragsesi localDragSession kami Dropsesi sesi , konteks lokal localContext , yang merupakan koleksi kami sollectionView dan objek lokal localObject , yang bisa kita lakukan dengan sendirinya ulang gambar gambar dari "Galeri" atau indexPath . Karena ini kita bisa mendapatkan dalam metode performDrop kelas GarbageView koleksi koleksi , dan menggunakannyaDataSource bagaimana ImageGalleryCollectionViewController dan Model imageGallery kamiController, kita bisa mendapatkan berbagai gambar gambar TYPE [ImageModel]:



Dengan bantuan lokalDragsesi localDragSession kamiDropsesi sesi kami mampu mendapatkan semua "drag" pada GarbageView Drag unsur item , dan mungkin ada banyak, seperti yang kita tahu, dan semuanya adalah gambar koleksi collectionView kami . MembuatDragelemen dragItems koleksi kamiCollection View, kami telah menyediakan untuk setiap "seret"DragelemendragItem objek lokal localObject , yang adalah gambaran dari gambar , tetapi kita tidak berguna selama pengumpulan reorganisasi internal CollectionView , tetapi "reset" Gambar Galeri "sampah dapat" kita sangat membutuhkan di fasilitas lokal localObject "drag" objek dragItem , setelah sekian lama kami tidak memiliki koordinator , yang dengan murah hati membagikan informasi tentang apa yang terjadi dalam koleksi collectionView . Oleh karena itu, kami ingin objek lokal localObject indeks indexPath dalam gambar berbagai gambar model kamiimageGallery . Membuat perubahan yang diperlukan dalam metode dragItems (di indexPath: indexPath) kelas ImageGalleryCollectionViewController :



Sekarang kita bisa mengambil setiap "pretaskivaemogo" elemen barang itu localObject , yang merupakan indeks indexPath dalam gambar berbagai gambar model kami imagegallery , dan mengirimkannya ke indeks array indeks dan array indexPahes dari gambar yang dihapus:



Mengetahui array dari indexes index dan array dari indexPahes dari gambar yang dihapus, dalam metode performBatchUpdateskoleksi koleksi kita menghapus semua gambar dihapus dari Model gambar dan dari koleksi koleksi :



Jalankan aplikasi, mengisi galeri dengan gambar baru:



Pilih sepasang gambar yang kita ingin menghapus dari galeri kami ...



... "membuang" mereka pada ikon dengan "sampah" ...



Mereka berkurang hampir ke 0 ...



... dan menghilang dari koleksi Collection View, bersembunyi di "tempat sampah":



Menyimpan gambar di antara mulai.



Untuk menyimpan Galeri Gambar di antara berjalan, kami akan menggunakan UserDefaults , setelah mengubah Model kami ke dalam JSONformat. Untuk melakukan hal ini, kita akan menambah kita Controllervariabel defailts var ...



..., dan dalam model ImageGallery dan ImageModel protokol Codable :



String String , array Array , URL , dan ganda sudah menerapkan protokol Codable , jadi kita tidak punya apa-apa lagi yang harus dilakukan untuk mendapatkan encoding kerja dan decoding ke Model ImageGallery dalam JSONformat.
Bagaimana cara mendapatkan JSONversi ImageGallery ?
Untuk membuat ini diperluas variabel json var , yang mengembalikan hasil upaya untuk mengubah diri, diri , melalui JSONEncoder.encode () dalam JSONformat yang:



Dan itu semua. Entah Data akan dikembalikan sebagai hasil dari konversi diri ke format JSON, atau nihil jika konversi ini gagal, meskipun yang terakhir tidak pernah terjadi, karena JENIS ini adalah 100% Dienkripsi . Digunakan Opsional variabel json hanya untuk alasan simetri.
Sekarang kami memiliki cara untuk mengonversi Model ImageGallery ke format DataJSON . Apakah variabel json memiliki Data TYPE ? yang dapat diingat di UserDefaults .
Sekarang bayangkan bahwa entah bagaimana kami berhasil mendapatkan JSONdata json , dan saya ingin membuat ulang dari mereka Model kami, sebuah instance dari struktur ImageGallery . Untuk melakukan ini, sangat mudah untuk menulis INITIALIZER untuk ImageGallery , yang argumen inputnya adalah JSONdata json . Inisialisasi ini akan menjadi inisialisasi "jatuh" (failable) Jika gagal diinisialisasi, maka crash dan kembali nihil :



Saya baru saja mendapatkan nilai newValue menggunakan decoder JSONDecoder , mencoba untuk men-decode data json yang diteruskan ke initializer saya, dan kemudian menetapkan sendiri .
Jika saya berhasil melakukan ini, maka saya mendapatkan contoh baru dari ImageGallery , tetapi jika upaya saya gagal, maka saya mengembalikan nol , karena inisialisasi saya "gagal".
Saya harus mengatakan bahwa di sini kita memiliki lebih banyak alasan untuk "gagal" ( fail), karena sangat mungkin bahwa JSONdata jsondapat rusak atau kosong, semua ini dapat menyebabkan "jatuh" ( fail) dari inisialisasi.

Sekarang kita dapat menerapkan BACA JSONdata dan model pemulihan imagegallery metode viewWillAppear kami Controller...



... serta entri dalam pengamat didSet {} sifat imagegallery :



Mari menjalankan aplikasi dan mengisi galeri kami gambar:



Jika kita menutup aplikasi dan buka lagi, kita dapat melihat galeri kami sebelumnya Gambar disimpan dalam UserDefaults .

Kesimpulan


Artikel ini menunjukkan betapa mudahnya untuk mengintegrasikan teknologi Drag & Dropke dalam iOSaplikasi menggunakan contoh aplikasi demo yang sangat sederhana "Galeri Gambar" . Ini memungkinkan untuk sepenuhnya mengedit Galeri Gambar, "melempar" gambar baru dari aplikasi lain di sana, memindahkan yang sudah ada dan menghapus yang tidak perlu. Dan juga untuk mendistribusikan gambar yang terakumulasi di Galeri ke aplikasi lain.

Tentu saja, kami ingin membuat banyak koleksi gambar tematik yang indah seperti itu dan menyimpannya langsung ke iPad atau iCloud Drive. Ini dapat dilakukan jika masing-masing Galeri tersebut ditafsirkan sebagai UIDocument yang disimpan secara permanen. Interpretasi seperti itu akan memungkinkan kita untuk naik ke tingkat abstraksi berikutnya dan membuat aplikasi yang bekerja dengan dokumen. Dalam aplikasi seperti itu, dokumen Anda akan diperlihatkan oleh komponen DocumentBrowserViewController , sangat mirip dengan aplikasi tersebut Files. Ini akan memungkinkan Anda untuk membuat gambar UIDocument dari jenis "Galeri Gambar" pada Anda iPaddan pada iCloud Drive, serta memilih dokumen yang diinginkan untuk dilihat dan diedit.
Tapi ini adalah subjek dari artikel selanjutnya.

PS Kode aplikasi demo sebelum implementasi mekanisme Drag & Dropdan setelahnya ada di Github .


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


All Articles