Welcome! This article will show you how to perform Drag and Drop on iOS. New in iOS 11, we have all the power of multitasking available for us developers. This includes dragging and dropping items inside our applications or between applications.

This article also presents a sample app called “Everation”, allowing you to get inspirational quotes or text from any other application (i.e: Safari) and add it as a text or quote note to your dashboard of inspirational texts.

Drag and Drop on iOS

Drag and Drop is a way of graphically move or copy data, within the same application or between applications.

Apple tries to transmit drag and drop to iOS in the context of its multi-gesture paradigm. Not only Drag and Drop on iOS can be done within multiple apps using the split view functionality of devices such as the iPad Pro. Also, it’s performed interactively, allowing you to manipulate the screen with all fingers. That supposedly allows you to manipulate other elements of the screen, change the split view apps, or even drag several types of items like text copied from Safari and images from your camera roll.

Honestly, while trying to perform such interactions, I found it kind of weird and unnatural for something more elaborate than just dragging and dropping a single piece of data.

Drag and Drop on iOS 11

As you can see in the video, I try to copy some text from Safari and then simultaneously some images from the camera roll into a note. While the images get to the note without a problem, the text disappears. Also, notice how the finger interaction in my right hand is kind of stressful when selecting multiple images.

So all in all, I think it’s a pretty useful feature. However, I don’t think people it’s going to be able to get the most out of this multi-finger, multi-gesture interactions (maybe centennials).

Support for Drag and Drop on iOS Devices

Drag&Drop is certainly a productivity feature. Thus, it makes more sense for the iPad. For this reason, Apple has decided to offer it on iPhone devices only in a limited form. Concretely, you can only use it in the same application.

Concepts Behind Drag and Drop on iOS

Drag and Drop on iOS has four differentiate phases:


The lift is when the user performs a long press, indicating the purpose of dragging and dropping something. A lift preview is generated for the item, and then the user starts dragging the finger.


The dragging is when the user is moving the object with the finger through the surface of the screen. During this phase, the preview of the item gets updated, and some interactions are allowed such as tapping another element to add it to the current dragging session.


Dropping happens when the user lifts the finger. At that moment, two things can happen, either the drag is canceled, or the drop takes places into its target.

Data Transfer

If the dragging process is not canceled and a drop takes place, the data transfer is the phase in which the destination requests the data to the source.

Drag and Drop Interactions

Both dragging and dropping are implemented as interactions and attached to a view. Most usually, that view is the main view of our UIViewController or one of its subviews. As a result, our way of communicating with those interactions is by means of delegates.

Implementing Dragging

For dragging, we have a class UIDragInteraction, that calls its UIDragInteractionDelegate (i.e: our view controller). This delegate has only one mandatory method: dragInteraction(:itemsForBeginning:), that should return the items to be included when the drag animation begins. You can return no items here, in which case the Drag&Drop gets canceled.

These items are instances of UIDragItem, model objects that contain a Drag Preview (how the items look like when it’s been dragged) and an Item Provider (the source of the data). If the drop takes place, this Item Providers will be asked to provide the data to appear in the destination target.

Implementing Dropping

In order to enable dropping, we need to define a UIPasteConfiguration property for the target view. Now all UIResponders, including UIView, can define a UIPasteConfiguration, allowing us to paste inside elements of a specific type.

let config = UIPasteConfiguration(typeIdentifiersForAcceptingClass: NSString.self)
view.pasteConfiguration = config

Here, when identifying the paste configuration, we need to provide the type of objects we will be accepting. In this case, it will be NSString.self for text. Note how we are using the Objective-C string instead of its Swift counterpart.

Next, we need to define the paste method. This method will be called both when we receive a paste from a copy/paste operation, and when we receive a drop from a drag&drop operation.

override func paste(itemProviders: [NSItemProvider]) {
   // manage both copy/paste and drag&drop

However, in order to have more control over the interaction, we usually want to become the UIDropInteractionDelegate for the UIDropInteraction.

The Drop Interaction Delegate

When the user lifts the finger after a drag, the drop operation can be canceled or accepted. If it gets canceled, an animation will be triggered showing the object going “back” to its initial position.

However, if the drop is accepted, the delegate will be called to perform the drop via the dropInteraction(:performDrop:). Only during this method is the delegate allowed to request the data that’s been dropped to present it in the proper view. This is done by asking the Item Provider for the Drag Items.

In order to accept the drop, we also need to implement the dropInteraction(:sessionDidUpdate:). This method is called when the drag enters the interaction’s view, moves inside it, or new items get added to the drag while inside the view. This method needs to return a UIDropProposal containing the operation to perform if the drop succeeds. Valid values to return are:

  • .cancel: if we want to cancel the operation.
  • .copy: if we want to copy the data when the drag enters the drop area. This is most of the times what you would want to return.
  • .move: similar to .copy, we use it within our application when we want the data to “move” instead of generating a new copy. Imagine that you have a list of items in your application and you want to allow drag & drop to move them along the list. Then, you would use .move instead of .copy, as the data is already there. You need to allow this by implementing UIDropSession’s delegate method allowsMoveOperation.
  • .forbidden: similar to .cancel, but used to indicate a temporary or contextual cancel. Use it for targets where you would normally allow a drop, but some situations (i.e: network unavailable, read-only folder, loading…) prevent the interaction from happening right now.

Performing The Drop

The drop takes place in the dropInteraction(:performDrop:) method. As we mentioned, here is where you can retrieve the data from the source. Let’s see an example of how to access text (NSString) elements.

func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
   session.loadObjects(ofClass: NSString.self) {
      for text in objects as! [NSString] {

Now let’s see a complete app making use of this technique.

The Everation App

Our sample application is very simple. It contains a dashboard with a collection view containing all the texts, quotes or notes we have collected from any other app. We allow drag and drop, as well as copy paste, so we can create a new everation from any piece of text in any other app.

Drag and Drop on iOS 11

You can see our “Drag and Drop on iOS” app in action in the video above. Let’s have a look at how to implement this.

Basic Structure

Our app only has one view controller, containing a collection view and a message with a “Paste” button. In our model, we define an enumeration called “EverItem”, with two cases:

  • First, a text item, containing a String.
  • Second, an image item, containing a UIImage.
enum EverItem {
   case text(content: String)
   case image(content: UIImage)

Also, we define a cell subclass for each of these two cases, to display either a label with a text or a UIImageView with the image. Finally, we set our collection view data source/delegate methods and add some functions to calculate the size of the rows to display to make them look cool at any screen configuration.

Up until here, our app is just the standard collection view app. Now, let’s start adding the drag and drop functionality.

External Copy VS Internal Movement

We need to differentiate two cases:

  • If we are copying or dragging and dropping a text or an image from another application.
  • If we are moving cells within our own application.

This last scenario will allow us to easily reorder our cells by dragging and dropping them across the collection view. In order to track these two scenarios, we’ll use two variables:

  • originIndexPath: index path for the cell we started to drag. If the drag comes from an external source (like another app), this will be nil.
  • destinationIndexPath: index path where we will drop our item. We will use this variable to insert the cell in the middle of the collection. If the cell gets dropped at the end of the collection view, or in no index path in particular, this will be nil.

Enabling Paste and Inserting the New Item

First, we need to allow our view controller to receive (paste) items of type String or UIImage. We can specify this by setting the UIViewController’s pasteConfiguration property.

pasteConfiguration = UIPasteConfiguration(acceptableTypeIdentifiers: 
   [kUTTypeText as String, kUTTypePlainText as String, kUTTypeImage as String]

Second, we define the paste method. All UIViewControllers, being UIResponders, can override it. In our case, we receive an array of item providers and will call a method called loadContent to take care of loading all the content from all those providers.

override func paste(itemProviders: [NSItemProvider]) {
   for itemProvider in itemProviders { loadContent(itemProvider) }

Next, we need to define this loadContent method to copy or move the new item we paste or drop into our collection view. Generally speaking, this item will come from a NSItemProvider, the source entity where the data comes from. Both Drag&Drop operations (via its session items) and the Copy/Paste (via its UIPasteBoard items) will give you access to these item providers.

Loading Content from an Item Provider

Thus, we will define our loadContent method to generate and add an item from an item provider:

func loadContent(_ itemProvider: NSItemProvider) {
    if itemProvider.canLoadObject(ofClass: UIImage.self) {
        itemProvider.loadObject(ofClass: UIImage.self) { object, error in
            if error != nil { print("Error loading image. \(error!.localizedDescription)"); return }
            DispatchQueue.main.async {
                // add item
                let image = object as! UIImage
                let newEveritem = EverItem.image(content: image)
                // clear paths
    } else if itemProvider.canLoadObject(ofClass: NSString.self) {
        itemProvider.loadObject(ofClass: NSString.self) { object, error in
            if error != nil { print("Error loading text. \(error!.localizedDescription)"); return }
            DispatchQueue.main.async {
                // add item
                let text = object as! NSString
                let newEveritem = EverItem.text(content: text as String)

Here, we basically ask if the provider has some strings or images for us (via its canLoadObject method), and then load it and create the appropriate type of EverItem.

Adding and Removing the Items in our Collection View

The addNewItem method depends on the values of originIndexPath and destinationIndexPath:

  • If originIndexPath is present, we are moving an item from within our collection view. Thus, we will delete the cell (and associated data item) from the collection view.
  • if destinationIndexPath is present, we are moving or copying the item to a specific position in the collection view. In that case, we will insert it in between (using
func addNewItem(_ item: EverItem) {
        // update data
        if let originRow = originIndexPath?.row { everitems.remove(at: originRow) }
        if let destinationRow = destinationIndexPath?.row { everitems.insert(item, at: destinationRow) }
        else { everitems.append(item) }

        // remove origin cell (if any)
        if originIndexPath != nil { collectionView.deleteItems(at: [originIndexPath!]) }
        // add cell at destination (if any) or at the end (otherwise)
        var destinationIndexPaths: [IndexPath]
        if destinationIndexPath != nil { destinationIndexPaths = [destinationIndexPath!] }
        else { destinationIndexPaths = [IndexPath(item: self.everitems.count - 1, section: 0)] }
        collectionView.insertItems(at: destinationIndexPaths)

    }) { (success) in
        // fallback to reload data if an error happened
        if !success { self.collectionView.reloadData() }
    // clear paths

Even though it might seem tedious, this method is actually quite simple. We just remove the items and associated cells from originIndexPath (if not nil), and then add the new items at the desired destinationIndexPath (if not nil) or at the very end of the collection view (otherwise). We wrap all these operations in a collectionView.performBatchUpdates to get a beautiful animation alongside our changes.

After adding the item, we need to set the origin and destination index paths to nil, via the clearPaths method.

Next, let’s have a look at how to control the dragging part of the equation.

The Drag Interaction

First, at viewDidLoad, we will set ourselves as the delegates of the dragging operation in collection view:

class ViewController: UIViewController, ..., UIDragInteractionDelegate { 
   func viewDidLoad() {
      collectionView.addInteraction(UIDragInteraction(delegate: self))

Then, the only mandatory item we need to provide for the delegate is the dragInteraction(:itemsForBeginning:). If we are managing a drag within our collection view, we will return here the item from the cell that’s being dragged. Otherwise, we return an empty array -because the interaction comes from an external app that provides the item-.

func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
    let point = session.location(in: interaction.view!)
    if let indexPath = collectionView.indexPathForItem(at: point) {
        // store our origin index path
        originIndexPath = indexPath
        let item = everitems[indexPath.row]
        let itemProvider: NSItemProvider
        switch item {
        case .image(let content):
            itemProvider = NSItemProvider(object: content)
        case .text(let content):
            itemProvider = NSItemProvider(object: content as NSString)
        let dragItem = UIDragItem(itemProvider: itemProvider)
        dragItem.localObject = indexPath
        return [dragItem]
    } else {
        originIndexPath = nil
        return []

Two important things to notice here.

First, in order to determine if we are dragging one of our cells, we first extract the interaction point by using session’s method location(in:). Then, with that point, we can use collection view’s method indexPathForItem(at:) to get the index path for that exact location, if any. In that case, we are dragging from one of our cells, and we can set originIndexPath.

Second, in that case, we create a UIDragItem to return by building an NSItemProvider with the concrete object contained in the cell (of type EverItem), and wrapping it inside a UIDragItem.

Polishing Touches: Dragging Animations

During the dragging interaction, the UIDragInteractionDelegate is asked to specify how the dragging animation will work. This implies how to display the items while they are dragged and the “placeholder item” that is left while we are animating the drag.

Let’s have a look at those methods:

First, dragInteraction(:previewForLifting:session:) will define the preview that will be shown underneath the user’s finger while we are animating the object. You should define this method for dragging and dropping operations within the app itself (i.e: we are moving a cell). Thus, in our case, we will return the cell that’s being dragged, or nil if the interaction comes from an external app.

func dragInteraction(_ interaction: UIDragInteraction, previewForLifting item: UIDragItem, session: UIDragSession) -> UITargetedDragPreview? {
    if let indexPath = item.localObject as? IndexPath {
        return UITargetedDragPreview(view: collectionView.cellForItem(at: indexPath)!)
    } else { return nil }

Next, we can define the animation for the placeholder that’s left in its original posotion while we drag the item. In our case, we can use a nice semi-transparent effect for the cell we are moving.

func dragInteraction(_ interaction: UIDragInteraction, willAnimateLiftWith animator: UIDragAnimating, session: UIDragSession) {
    animator.addCompletion { position in // during dragging, show semi-transparent image of cell being dragged.
        if position == .end {
            self.setAlpha(0.5, forCellAtItems: session.items)

Finally, whether the interaction finishes successfully or gets canceled, we want to return the cell back to full opacity.

❤️ Enjoying this post so far?

If you find this content useful, consider showing your appreciation by buying me a coffee using the button below 👇.

Buy me a coffeeBuy me a coffee
func dragInteraction(_ interaction: UIDragInteraction, item: UIDragItem, willAnimateCancelWith animator: UIDragAnimating) {
    animator.addAnimations { // cancel animation should get original cell image back to alpha = 1
        self.setAlpha(1.0, forCellAtItems: [item])

func dragInteraction(_ interaction: UIDragInteraction, session: UIDragSession, willEndWith operation: UIDropOperation) {
    if operation == .copy { // items being copied from another app. Set dragged items alpha to 1.
        setAlpha(1.0, forCellAtItems: session.items)

The Drop Interaction

Similarly, the first thing we need to take care of for the drop interaction is setting ourselves as its delegate.

class ViewController: UIViewController, ..., UIDropInteractionDelegate {
   func viewDidLoad() {
      collectionView.addInteraction(UIDropInteraction(delegate: self))

Then, we need to specify what type of objects we accept. We do that in the dropInteraction(:canHandle:). We will return true if we are receiving an image or a text. In order to do that, we use the method canLoadObjects from UIDropSession.

func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
   return session.canLoadObjects(ofClass: UIImage.self) || session.canLoadObjects(ofClass: NSString.self)

Next, we also need to update our dropping session when appropriate. We need to distinguish if we are copying the item from an external application, or moving it within our own collection view. Also, we update our destinationIndexPath if it’s above one of our cells.

func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
    let operation: UIDropOperation
    if session.localDragSession == nil { // drag from an external app initializing. Copy.
        operation = .copy
    } else { // local drag from our own app (collection view). Just move.
        operation = .move
    // upate indexpath if needed.
    let hitPoint = session.location(in: interaction.view!)
    destinationIndexPath = collectionView.indexPathForItem(at: hitPoint) ?? nil

    return UIDropProposal(operation: operation)

Finishing The Dropping Operation

Finally, we drop the item in the dropInteraction(:performDrop:) method:

func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
    // calculate destination index path
    let hitPoint = session.location(in: interaction.view!)
    destinationIndexPath = collectionView.indexPathForItem(at: hitPoint) ?? nil

    for dragItem in session.items {

As you can see, this method only updates the destination index path (just in case) and for every item contained in the session, calls loadContent.

The result is a dynamic, rich application that can receive text and images from external applications or move the ones already there. Drag and Drop on iOS really adds a new level of interactivity to the application.

In Summary

In this article, you learned about drag and drop on iOS 11, and how to add it to your applications.

Drag and Drop on iOS can really help your interfaces become more interactive and dynamic. As always, you must implement it only if it makes sense for your user experience, not just for the sake of it. Nevertheless, adding it can really help your app stand out from the competition.

If you want to know a little more about how I configured the collection view to display a specific number of rows, no matter the device, have a look at the Flawless collection and table views tutorial.

Also, as always, you can get the full source code for the project from my Github repository.