Drag & Drop
mechanism, working in iOS 11
and iOS 12
, is a way to graphically asynchronously copy or move data both within one application and between different applications. Although this technology is about 30 years old, it has literally become a “breakthrough” technology on iOS
due to the fact that when dragging and dropping something in iOS
, multitouch
allows you to freely interact with the rest of the system and collect data for resetting from different applications.iOS
makes it possible to capture multiple items at once. Moreover, they do not have to be easily accessible for selection: you can take the first object, then go to another application and grab something else - all the objects will be collected in a “stack” under your finger. Then call the universal dock on the screen, open any application there and capture the third object, then go to the screen with applications running and, without releasing the objects, drop them into one of the open programs. Such freedom of action is possible on the iPad
, on the iPhone
, the range of Drag & Drop
in iOS
limited to the scope of one application.Safary
, Chrome
, IbisPaint X
, Mail
, Photos
, Files
, etc.) already have Drag & Drop
. In addition, Apple
provided developers with a very simple and intuitive API
for embedding the Drag & Drop
mechanism in your application. The Drag & Drop
mechanism, like the gestures, works on UIView and uses the concept of “interactions” Interactions , which are a bit like gestures, so you can think of the Drag & Drop
mechanism as just a really powerful gesture.API Drag & Drop
improved and raised to a higher level of abstraction in the sense that the Collection View
itself helps you with the indexPath of the collection item that you want to drag. Drag
. It knows where your finger is and interprets it as the indexPath of a collection item that you are dragging Drag
at the moment or as the indexPath of a collection item where you Drop
something Drop
. So the Collection View
collection provides you with indexPath , but otherwise it’s absolutely the same API Drag & Drop
as for a regular UIView .Drag & Drop
process on iOS
has 4 different phases:lift preview
) of the specified element is formed, and then the user begins to drag ( Dragging
) his fingers.lift preview
) for this object can be modified (a green plus sign "+" or another sign appears) ...Drag
object will be destroyed, or the Drop
object will be reset at the destination.Drag & Drop
mechanism into your iOS
app.Collection View
collection with the ability to fill ourselves with images OUTSIDE, as well as reorganize the elements INSIDE yourself using the Drag & Drop
mechanism. In addition, this mechanism will be used to reset unnecessary elements of the Collection View
into a “trash can”, which is a common UIView and is represented by a button on the navigation bar. We can also share images from other applications, such as Notes
or Notability
or Mail
or the Photo Library ( Photo
), using the Drag & Drop
mechanism.Drag & Drop
mechanism in the demo application “Gallery of Images”, I will briefly go through its main components.UI
) of the Image Gallery application is very simple. This is the “screen capture” Image Gallery Collection View Controller
inserted into the Navigation Controller
:Image Gallery Collection View Controller
, which is supported by the ImageGalleryCollectionViewController class with the Image Gallery Model as a variable var imageGallery = ImageGallery () :URL
url of the image location (we are not going to store the image itself) and its aspect ratio aspectRatio :subclass
ImageCollectionViewCell of the UICollectionViewCell class:Public API
of the ImageCollectionViewCell class is the imageURL image URL . As soon as we install it, our UI
updated, that is, the data for the image on this imageURL is selected asynchronously and displayed in a cell. While data is being sampled from the network, the spinner activity indicator is working, indicating that we are in the process of retrieving data.memory cycle
)?” We don’t have an explicit “cycling memory reference” ( memory cycle
), because self itself does not have a pointer to this closure.Collection View
collection are reusable thanks to the dequeueReusableCell method. Each time a cell (new or reused) hits the screen, the image is launched asynchronously from the network (the spinner of the spinner activity indicator is spinning at this time).UI
this collection cell is updated. But we don’t wait for the image to load, we continue to scroll through the collection and the collection cell that we’ve noticed goes off the screen without updating our UI
. However, a new image should appear from below, and the same cell that has left the screen will be reused, but for another image, which may load quickly and update the UI
. At this time, the image download previously launched in this cell will return and the screen will be updated, which will lead to an incorrect result. This is because we are running different things working on the network in different threads. They return at different times.URL
url that caused the download of this data and compare it with what the user wants to have in this cell at the moment, that is, imageURL . If they do not match, then we will not update the UI
cell and wait for the image data we need:Collection View
can confuse the user a bit:Controller
so that it corrects the aspect ratio of its aspectRatio for the corresponding indexPath in its ImageGallery model . This is an interesting task, there are many ways to solve it, and we will choose the easiest of them - using Optional closure var changeAspectRatio: (() -> Void)? . It can be nil and need not be installed if there is no need for it:Controller
in the cellForItemAt method:Collection View
image Collection View
, on our UI
we placed the Bar Button
with a custom GarbageView image, containing the “trash can” as a subview on the navigation bar:Bar Button
Trash
button:Github
in the ImageGallery_beginning
folder. If you launch this version of the Image Gallery application, you will see the result of the application’s work on the test data, which we will subsequently delete and fill in the Image Gallery only OTHER:Drag & Drop
mechanism in our application is as follows:Collection View
ability to drag Drag
UIImage from outside both locally and from it,Collection View
images to take drag-drawn Drag
from outside or locally UIImage images,Collection View
and remove them from the Collection View
collection.Drag & Drop
mechanism implemented. It is located on Github
in the ImageGallery_finished
folder.Drag & Drop
mechanism in your Collection View
ensured by two new delegates.Drags
.Data transfer
) and custom animation settings for Drop
, as well as other similar things.Drag
dragging or Drop
, but you can use both protocols at the same time and drag Drag
and drop Drop
, which opens up additional functionality. Drag & Drop
mechanism for changing the order of elements in your Collection View
.Drag
Elements From Collection View
Drag
protocol is very simple, and the first thing you should always do is install yourself, self , as a delegate to dragDelegate :Fix
button:Drag
system WHAT we are dragging. The itemsForBeginning method is called when the user starts dragging a cell of the cell collection.Collection View
added an indexPath to this method. This will tell us which element of the collection, which indexPath , we are going to “drag”. This is really very convenient for us, since it is up to the application to take responsibility for using the session and indexPath arguments to figure out how to handle this Drag dragging.Drag
dragging is initialized; if the empty array [] is returned, the Drag
dragging is ignored.Drag & Drop
operation is completely asynchronous. When you start Drag dragging, it’s really a very lightweight object ( lift preview
), you drag it everywhere, and nothing happens during that dragging. But as soon as you “drop” Drop
your object somewhere, then being an ItemProvider , you really should supply your “dragged” and “thrown” object with real data, even if it takes a certain amount of time.iOS
and that are itemPoviders , such as NSString
, for example, which allows you to drag text without fonts. Of course, this is a UIImage image. You can select and drag UIImages everywhere. NSURL class, which is absolutely wonderful. You can go to the Web
page, select the URL
and “throw” it wherever you want. This may be a link to the article or the URL
for the image, as it will be in our demo. These are the color classes of UIColor , the MKMapItem map element , the CNContact contact from the address book, a lot of things you can select and drag. All of them are itemProviders .Collection View
cell with the indexPath , which helps me to select a cell , get an Outlet
imageView from it and get its image .Collection View
about the cell cell for the item element corresponding to this indexPath .Collection View
only works for visible ( visible
) cells, but, of course, it will work in our case, because I’m dragging the Drag
item on the screen and it is visible.subclass
. And if it works, then I get an Outlet
imageView , from which I take its image . I just captured the image image for this indexPath .Drag
locally, that is, inside your application, then you do not need to go through all this code associated with itemProvider , through asynchronous data retrieval. You do not need to do this, you just need to take localObject and use it. This is a kind of “short circuit” with a local “drag and drop” Drag
.Drag
beyond our collection Collection View
to other applications, but if we “drag and drop” Drag
locally, we can use localObject . Next, I return an array consisting of a single dragItem element .Drag
is canceled.Drag
session session . In our case, it will be a collectionView collection.and it is useful to us afterwards:Drag
, you can add more items items to this "drag and drop", just doing the gesture tap on them. As a result, you can drag a Drag
lot of items at once. And it is easy to implement using another method delegate UICollectionViewDragDelegate , very similar to the method itemsForVeginning , a method called itemsForAddingTo . The itemsForAddingTo method looks exactly the same as the itemsForVeginning method , and returns exactly the same thing, because it also gives us the indexPathof what the user “tapped” on during the “drag Drag
and drop” process , and it is enough for me to get an image from the cell where the user “tapped” and return it.Drag
.Photos
, as you see a green plus sign "+" in the upper left corner of the "drag and drop" image. I can perform a tap gesture on one more image of "Artik" from the collection Collection View
...Photos
:Photos
has a mechanism already built in Drag & Drop
, everything works fine, and this is cool.Drag
and “reset” the Drop
Gallery image to other applications, I did not have to do much in my application, except for the delivery of the image as an array [UIDragItem] . This is one of the many great features of the mechanism.Drag & Drop
- it is very easy to make it work in both directions.Drop
images In a collectionCollection View
Drop
part for my collection Collection View
so that we can “drop” Drop
any “draggable” images INSIDE of this collection. “Dragged” image can “come” both from outside and directly from inside of this collection.Fix
, and before us appear the mandatory methods of this protocol. In this case, we are told that we must implement the performDrop method :Drop
. In fact, I'm going to implement the performDrop method last, because there are a couple of other highly recommended Apple
methods that need to be implemented for the Drop
part. These are canHandle and dropSessionDidUpdate :ollection View
, and besides, we will not try to dump something we do not understand.ollection View
. But this method ollection View
looks exactly the same as a similar method for a regular UIView , there is no indexPath . We just need to return session.canLoadObjects (ofClass: UIImage.self) , which means that I accept the “reset” of the objects of this class PAS in my collection ollection View
:Drop
image into my collection Collection View
OUTSIDE.Drop
image is dumped INSIDE a collection Collection View
, when a user reorganizes his own elements of items using a mechanism Drag & Drop
, then a single UIImage image is enough , and the implementation of the canHandle method will look like this.Drop
image “dumping” happens EXTERNAL, then we need to process only those “drag and drop” Drag
that represent the UIImage image along with URL
for this image, since we are not going to store the UIImage images directlyin the model. In this case, I will return true in the canHandle method only if a pair of session.canLoadObjects conditions (ofClass: NSURL.self) && session.canLoadObjects (ofClass: UIImage.self) are simultaneously executed & :Drop
the session the session , both its local Drag
session localDragSession . This local Drag
session in turn has a local context localContext .Drag
delegate UICollectionViewDragDelegate :Drop
EXTERNAL.Drag
onto our collection Collection View
. Otherwise, it doesn’t make sense at all to talk about “dumping” Drop
.Drop
, then even before the user lifts his fingers from the screen and a real “reset” occurs Drop
, we must inform the delegate UICollectionViewDropDelegate about the UIDropProposal proposal to perform a reset iOS
using the dropSessionDidUpdate method .Drop
Drop
clause that can have the values .copy or .move or .cancel or .forbidden for the operation argument . And these are all the possibilities that we have in the usual case when dealing with the usual UIView .Collection View
goes further and offers to return the specialized UICollectionViewDropProposal clause , which is subclass
of the UIDropProposal class and allows, in addition to the operation operation, to specify an additional intent parameter for the collection Collection View
.Collection View
whether we want the “reset” element to be placed inside an already existing cell or whether we want to add a new cell. See the difference? In the case of a collection,Collection View
we must report our intent intent .Collection View
with this small piece of information that we provided to it.Safari
with a search engineGoogle
, and this image has a green "+" sign on top, indicating that our Gallery of Images is ready not only to receive and copy this image along with it URL
, but also to provide a place inside the collection Collection View
:Safari
, and “Dragged” images will already be 3:Drop
these images, they will not be placed in our Gallery, but will simply return to their former places, because we have not yet implemented the performDrop method .Collection View
already knows what I want to do.Collection View
is absolutely wonderful thing for the mechanism.Drag & Drop
, it has very powerful functionality for this. We barely touched it, having written 4 lines of code, and it already advanced far enough in the perception of “reset” Drop
.Drop
, then in the performDrop method we have to update our Model, which is imageGallery Image Gallery with a list of images images , and we need to update our collectionView visual collection .Drop
.Drop
from my collectionView , then I need to “reset” the Drop
collection item at the new location and remove it from the old location, because in this case I move ( .move ) this collection item. This is a trivial task. IfDrop
from another application, we must use the itemProvider property of the “drag and drop” item element to select data.Drop
in the collectionView collection , the collection provides us with the coordinator coordinator. The first and most important thing that the coordinator coordinator tells us is the destinationIndexPath , that is , the destination-point indexPath of the “reset” Drop
, that is, where we will be “reset”.Collection View
that is not between any existing cells , so that it may well be nil . If this is the situation, then I create an IndexPath with the 0th item element in the 0th section of the section .Drop
. We must go through all the “reset” elements of coordinator.items provided by the coordinator coordinator . Each item from this list has the UICollectionViewDropItem TYPE and can provide us with very interesting pieces of information.Drag
is done from myself, self, and the source of the drag Drag
is the collection item with the indexPath equal to sourceIndexPath :Drag & Drop
, and the task becomes trivial. All I need to do is update the Model so that the source and destination point are swapped, and then update the collectionView collection to remove the collection item from sourceIndexPath and add it to the collection from destinationIndexPath .Drag & Drop
works not just in the same application, but also in the same collectionView collection , and I can get all the necessary information using the coordinator coordinator. Let's implement this simplest local case:Drop
images into the “trash can”, which is in the same application, but is not the same collectionView collection . Now two IndexPathes are enough for me : the source sourceIndexPath and the destination point destinationIndexPath .Drag
, because it resets the whole "world" of our gallery of images, which is very bad, I do not. Instead, I'm going to remove and insert items.items separately:closure
) and within that closure I can place any number of these deleteItems , insertItems , moveItems and all I want:Drop
:Drag
and drop” EITHER and “drop” "Drop
Does this information become available instantly? No, you select data from the “dragged” thing ASYNCHRONOUS. But what if the sample takes 10 seconds? What will the collection do at this time ollection View
? In addition, the data may not come at all in the order in which we requested it. To manage this is not at all easy, and in this case it Apple
proposed a ollection View
completely new technology for using substitutes Placeholders
.Collection View
substitute in your collection Placeholder
, and the collection Collection View
manages it all for you, so all you have to do when the data is finally selected is to ask the substitute to Placeholder
call its context placeholderContextand inform him that you received the information. Then update your Model and the context of the placeholderContext AUTOMATICALLY swap a cell cell with a placeholder Placeholder
for one of your cells cells , which corresponds to the type of data you received.Placeholder
and which you get from the coordinator coordinator by asking to “reset” Drop
the item element on the placeholder Placeholder
.Drop
is item.dragItem , where item is the for element of the loop, since we can “throw” Drop
many coordinator.items objects . We “throw” them one by one. So item.dragItem is what we “drag” Drag
and “throw” Drop
. The next argument to this function is the placeholder, and I will create it using the initializer UICollectionViewDropPlaceholder :Placeholder
, that is, insertionIndexPath , as well as the reuseIdentifier cell identifier .Placeholder
. The coordinator coordinator does not have a “pre -assembled ” cell for the locator.Placeholder
. It is YOU who must decide on this cell . Therefore, a reuse cell identifier reuseIdentifiercell is requested with yours storyboard
so that it can be used as a PROTOTYPE.storyboard
to create this thing.storyboard
and create a cell cell for the placeholder Placeholder
. To do this, we just need to select a collection Collection View
and inspect it. In the very first field, Items
I change 1
to2
. This immediately creates a second cell for us, which is an exact copy of the first one.ImageCell
, set the identifier “ DropPlaceholderCell
”, remove all UI
elements from there , including Image View
, since this PROTOTYPE is used when the image has not yet arrived. Add a new activity indicator from the Objects Palette Activity Indicator
; it will rotate, making it clear to users that I am expecting some “discarded” data. Also change the background color Background
to understand that when "Reset" from the outside image works exactly this cell cell as prototypes:Outlets
control:Activity Indicator
so that it starts animating from the very beginning, and I wouldn’t have to write anything in the code to run it. To do this, click on the options Animating
:DropPlaceholderCell
, go back to our code. Now we have an excellent replacement locator Placeholder
, ready to go .Placeholder
and our “native” data cell, and we will make changes in the Model.main queue
. And, unfortunately, we had to switch to main queue
using DispatchQueue.main.async {} in order to “catch” the aspect ratio of the image in the local variable aspectRatio .Placeholder
with a cell by calling the normal cellForItemAt method .Collection View
can very much happen. New cells can be added cells , everything happens fairly quickly.URL
image imageURL from the corresponding by provider , an error might have been received error instead by provider , we have to let them know the context placeholderContext , you need to destroy this a placeholder Placeholder
, because we are all the same, we can not to get other data:URLs
that comes from places like Google
, in fact, they need minor transformations to get a “clean”URL
for the image. How this problem is solved can be seen in this demo application in a file Utilities.swift
on Github .URL
image, we use the imageURL property from the URL class :Collection View
.ImageGallery
and Safari
with a search engine Google
. In Google
we are looking for images on the theme "Dawn" (sunrise). In Safari
already builtDrag & Drop
mechanism, so we can select one of these images, hold it for a long time, move it a little and drag it to our Gallery of Images.Placeholder
:Placeholder
disappears:Placeholder
:Controller
Drag
and Drop
delegate and add the pinch gesture recognizer , which controls the number of images on the line:Drop
in the performDrop method ...Placeholder
works ...Placeholders
working ...Drop
in the trash can presented on the navigation bar on the right. As described in the “Image Gallery” demo application features section, the trash can is represented by the GabageView class , which inherits from UIView, and we have to teach it to take images from our collection ollection View
.Drop
Gallery images into the trash can.Drop
Drop
sessionDidUpdate and performDrop .Collection View
, we do not have any additional information in the form of the drop- point indexPath .Drag
, which are UIImage images. Therefore, I will return true only if session.canLoadObjects (ofClass: UIImage.self) :Drop
. And I am ready to accept only the “drag and drop” LOCALLY object of the UIImage image TYPE , which can be “dumped” Drop
anywhere inside my GarbageView . My GarbageView will not interact with images dumped EXIT. Therefore, I analyze using the variable session.localDragSession, whether there is a local “drop” Drop
, and return the “reset” clause in the form of the UIDropProposal constructor with the operation argument set to .copy , because ALWAYS LOCAL drag and drop Drag
in my application will come from the collection Collection View
. If there is a “drag Drag
and drop” and “reset” Drop
OUTSIDE, then I return the “reset” clause in the form of the UIDropProposal constructor with the operation argument set to .fobbiden , that is, “forbidden” and we will get the sign of the “reset” prohibition instead of the green plus sign .Collection View
.Drop
to another place and at the same time transforming the “discarded” object during animation:Drop
, and I (like GarbageView ) receive the message performDrop . In the message performDrop we perform the actual "reset" Drop
. Honestly, the image itself, discarded on the GarbageView , no longer interests us, since we will make it almost invisible, most likely the fact that the “reset” is complete Drop
will signal that we remove this image from the collection Collection View
. In order to do this, we need to know the collection itself and collection and indexPathdumped image in it. Where can we get them from?Drag & Drop
takes place in a single application, it is available to us all the local: local Drag
session localDragSession our Drop
session the session , the local context localContext , which is our collection of sollectionView and local object localObject , which we can do by itself is reset image image from "Gallery" or indexPath . Because of this we can get in the method performDrop class GarbageView collection collection , and using itdataSource how ImageGalleryCollectionViewController and Model imageGallery ourController
, we can get an array of images of images TYPE [ImageModel]:Drag
session localDragSession ourDrop
session session we were able to get all the "drag" on GarbageView Drag
elements of items , and there may be a lot, as we know, and all of them are images of our collectionView collection . Creating theDrag
elements ofour collection's dragItemsCollection View
, we provided for each “overtightened”Drag
element.dragItem local object localObject , who is the image of image , but it is we do not come in handy during internal reorganization collection CollectionView , but the "reset" Image Galleries "trash can" we desperately need in the local facility localObject "drag" object dragItem , after all this time we do not have a coordinator coordinator who so generously shares information about what is happening in the collectionView collection . Therefore, we want the local object localObject to be the indexPath in the image array images of our ModelimageGallery . Make the necessary changes in the method dragItems (at indexPath: IndexPath) class ImageGalleryCollectionViewController :Collection View
, hiding in the "garbage can":JSON
format. To do this, we add var defailts ... ... to our Controller
variable , and the Codable protocol in the ImageGallery and ImageModel structures : String strings , Array , URL and Double arrays already implement the Codable protocol , so we don’t have to do anything else to make the encoding work and decoding for the ImageGallery model in the format.JSON
JSON
version of ImageGallery ?JSON
format:JSON
, or nil if this conversion cannot be performed, although the latter never happens because this TYPE is 100% Encodable . The Optional json variable is used simply for symmetry reasons.JSON
. At the same time json variable has TYPE Data? which can be memorized in UserDefaults .JSON
data , and I would like to recreate our Model, an instance of the ImageGallery structure, from them . To do this, it is very easy to write an INITIALIZER for ImageGallery , whose input argument is json data . This initializer will be a “falling” initializer (JSON
failable
). If it fails to initialize, then it “falls” and returns nil :fail
), because it is quite possible that the jsonJSON
datamay be corrupted or empty, all of which can lead to the “fall” ( fail
) of the initializer.JSON
data and recovery model imagegallery the method viewWillAppear our Controller
...Drag & Drop
in an iOS
application. This allowed us to fully edit the Gallery of Images, "throwing" there new images from other applications, moving existing ones and removing unnecessary ones. And also distribute images accumulated in the Gallery to other applications.Files
. It allows you to create UIDocument documents of the “Gallery of Images” type both on your iPad
and on iCloud Drive
, as well as select the necessary document for viewing and editing.Drag & Drop
and after is on Github .Source: https://habr.com/ru/post/425817/
All Articles