Understanding UIDropInteractionDelegate

Similar to how UIDragInteractionDelegate works, UIDropInteractionDelegate is used to respond to different stages in the drag and drop life cycle. Even though UIDropInteractionDelegate has no required methods, there are at least two methods that you should implement to support drag and drop. The first method is dropInteraction(_:sessionDidUpdate:).

As soon as the user moves their finger on top of a drop target, the drop target is asked whether it can handle a drop with the contents from the current drop session. To do this, dropInteraction(_:canHandle:) is called on the drop target. Assuming the data can be handled, dropInteraction(_:sessionDidUpdate:) is called next. You must return an instance of UIDropProposal from this method. A drop proposal lets the session know what you'd like to happen if the drop is executed at some point. For instance, you can make a copy proposal or a move proposal. The UI surrounding the contents that are being dragged will update accordingly to let the user know what will happen if they perform the drop.

Now let's say your app can only handle objects of a particular type. You should implement dropInteraction(_:canHandle:). You can use this method to inspect whether the drop session contains items that are relevant for your app to handle. An example of this looks as follows:

func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
  for item in session.items {
    if item.itemProvider.canLoadObject(ofClass: UIImage.self) {
      return true
    }
  }

  return false
}

This example searches for at least one image in the current drop session. You can use canLoadObject(ofClass:) on NSItemProvider to figure out whether the item provider contains an instance of a particular class, in this case, UIImage. If your app restricts drag and drop to the same application, you might not need to implement this method because you know exactly what items in your app are draggable. It is recommended that you always make sure the session can be handled by the drop target.

The second method you should always implement is dropInteraction(_:performDrop:). If you don't implement this method, your app doesn't have a proper implementation of the drop interaction. Once the user drops an item onto a drop target, they expect something to happen. dropInteraction(_:performDrop:) is the perfect place to do so.

An example implementation of dropInteraction(_:performDrop:) could look as follows:

func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
  for item in session.items {
    if item.itemProvider.canLoadObject(ofClass: UIImage.self) {
      item.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] item, error in
        // handle the item
      }
    }
  }
}

The preceding code loops through all items in the drop session. If an item can provide an image, the image is loaded. Note that loadObject(ofClass:) uses a callback. This means that the data is loaded asynchronously. The dropped data could be huge and, if it were loaded and processed on the main thread by default, it would make your app freeze for a while. Because Apple made loadObject(ofClass:) asynchronous, your app's responsiveness is guaranteed, and your users won't notice any freezes or lag.

Just like a drag session, a drop session typically has three stages:

Apple has not defined the stages of the drag and drop experience as you have just learned them. Dividing the experience into the preceding steps is merely intended to help you to grasp the life cycle of drag and drop. If you want to learn everything about the drag and drop life cycle, make sure to check out the documentation for both UIDropInteractionDelegate and UIDragInteractionDelegate.

Alright, now that you know what drag and drop looks like in theory, let's see what it looks like in practice!