Implementing drag & drop for Collection View

A quick loot at implementing drag and drop capabilities in your app using Collection View.

While working on SwitchBuddy for iPad I got the idea for drag & drop which would allow users to open the app with Files for example side by side and then drag images from my app to the Files app.

Turns out getting basic drag & drop is not complicated. In this post, we will look at minimal implementation you can use as a starting point.

Collection View with drag & drop

To implement drag and drop functionality for UICollectionView we have to implement provided delegate methods.

These are part of UICollectionViewDragDelegate protocol and collection view has the dragDelegate property we can set our view controller to.

collectionView.dragDelegate = self

Since I needed just the "drag" feature to allow users drag images out of my app and not vice versa, I had to implement just a single method.

This one:

func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {

}

Your job is to somehow return an array of UIDragItem which in turn is a wrapper around NSItemProvider which wraps the actual data from our app. NSItemProvider supports a handful o data types out of the box, for example UIImage, UIColor, strings and urls. You can also pass file url when creating the object.

So for the image, implementation of the method above can look like this:

func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
    let provider = NSItemProvider(object: image)
    provider.suggestedName = photo.fileName

    let dragItem = UIDragItem(itemProvider: provider)
    return [dragItem]
}

I have omitted the part of implementation which gets the image, since this is going to be pretty specific for your app. In my case, since I am loading images from urls into the cells, I first get a cell, cast it to correct type and then get the image form image view.

Also note the provider.suggestedName line which lets you control the filename Files and other app will use when saving dropped files.

For my app, this was enough. As I said, I care only about the "drag" part because it does not make sense to drag images into my app.

Implementing "drop"

In case you want to have both drag and drop, here is a short paragraph about how to implement this.

First you need to conform to UICollectionViewDropDelegate - assuming you want to perform the drop onto collection view. And set it:

collectionView.dropDelegate = self

And then there is single required method to implement:

func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {

}

Your job is to look at the coordinator object and decide how to handle the drop. This is pretty complex and greatly depend on the result you want. There are a few properties you should check.

session is the UIDropSession and can tell you, for example which items are dropped using the hasItemsConforming method.

items contains the dropped items. (Surprising, I know). They are of type UICollectionViewDropItem and in turn wrap UIDragItem with the dragItem property, which finally has the itemProvider (NSItemProvider).

destinationIndexPath - this informs you where the item(s) have been dropped by the user.

proposal - this is UICollectionViewDropProposal with intent property. This tells you if dropped items should be inserted (.insertAtDestinationIndexPath) or incorporated (=combined) (.insertIntoDestinationIndexPath). Again this shows just how much depends on the experience you want and on the drop source.

I think for the simple cases, you can ignore most of these properties and just load the items.

Filip Němeček profile photo

WRITTEN BY

Filip Němeček @nemecek_f

iOS blogger and developer with interest in Python/Django. Telling other devs' stories with iOS Chat.