WWDC Highlights Part 2 — Drag and Drop

By Kyle Balogh

In my first WWDC post, I mentioned an overview of the highlights from WWDC 2017. In this post, I dive into the new drag and drop API and demonstrate how to create a drop zone app.

*Note: This app was written using iOS 11 beta 1. It is possible that the implementation may change by the time iOS 11 is officially released.

Drag and Drop

Productivity was one of the highlights of iOS 11 changes introduced at WWDC. One API that Apple is calling on developers to implement in their apps is drag and drop. It is a feature that the iPad will initially benefit the most from (given the iPad specific changes in iOS 11), but it looks like it could play a more important role down the road on iPhone as well. With this in mind, I thought it would be a good idea to familiarize myself with the new API.

Here are some of the WWDC sessions to get started with:

Introducing Drag and Drop

Data Delivery with Drag and Drop

Drag and Drop with Collection and Table View

Mastering Drag and Drop

Modern User Interaction on iOS

What’s New in Cocoa Touch

Six sessions that discuss drag and drop! Seems important.

To play around with drag and drop, I decided to create a drop zone app that pulls the last image in the user’s photo library to populate an image view. The image view will serve as the drag/drop zone and allow the user to drag the default image out of the app, as well as images from other apps, like Safari.

Let’s get started by opening Xcode and creating a new app (Single View App will suit our needs).

Photos Implementation

Next, we need to setup the project to allow access to photos. Open the Info.plist and add a NSPhotoLibraryUsageDescription, something like “Required for access to photo and video”.

Now we can jump into code. For simplicity, we will work from within the ViewController and will not setup dedicated classes for things like interacting with photos.

With that in mind, import Photos in your ViewController.

import Photos

Add an image view to the Main.storyboard and hook it up in ViewController using an IBOutlet.

import photo

Next, we need a function that will update the image view with the last photo in our photo library.

func updateImage() {
guard let lastAsset = PHAsset.fetchAssets(with: nil).lastObject else {
print("Error: Unable to fetch last asset")
return
}

PHImageManager.default().requestImage(for: lastAsset,
targetSize: imageView.frame.size,
contentMode: PHImageContentMode.aspectFit,
options: nil) { (image, _) in
guard let image = image else {
return
}
self.imageView.image = image
}
}

Before we call updateImage, we need to make sure the user has authorized the app to access their photo library. We can create a function called requestPhotoAuthorizationIfNeeded that handles this.

unc requestPhotoAuthorizationIfNeeded(completion:
@escaping (PHAuthorizationStatus)->()) {
if PHPhotoLibrary.authorizationStatus() == .authorized {
completion(PHPhotoLibrary.authorizationStatus())
return
}

PHPhotoLibrary.requestAuthorization { (status) in
completion(status)
}
} [

Then we can add the following to ViewController.viewDidLoad().

requestPhotoAuthorizationIfNeeded() { status in
if status != .authorized {
// Add better auth handling for a real app
print("Error: Photo access required")
return
}

DispatchQueue.main.async {
self.updateImage()
}
}[

At this point, we should now be populating the image view with the last photo in the photos library. Launch the app on your iPad, allow photo access, and it should look something like this:

Photo Access

Drag Implementation

And now for adding drag support. First, add a UIDragInteraction to the view you want to enable drag on. In this case, we want to add it to the image view because the image view could be smaller than the containing view and we do not want the drag snapshot to include white space around the image.

Note: Make sure user interaction is enabled for the image view.

func setupDragInteraction() {
let dragInteraction = UIDragInteraction(delegate: self)
imageView.addInteraction(dragInteraction)
}[

Before running the app we need to implement dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession). In this delegate method, we need to return the content we want to be copied from the app — the image view image in this case.

extension ViewController: UIDragInteractionDelegate {
func dragInteraction(_ interaction: UIDragInteraction,
itemsForBeginning session: UIDragSession) -> [UIDragItem] {
guard let image = imageView.image else {
return []
}
let itemProvider = NSItemProvider(object: image)
let dragItem = UIDragItem(itemProvider: itemProvider)
return [dragItem]
}
}

With just those few lines of code, you can now drag the image from your app to other apps that accept image drops. Test it out by opening messages side by side with the app and drag from the image view into messages.

Drag and Drop Photo

Pretty neat!

Drop Implementation

Since that was so easy, let’s go ahead and add drop support. Unsurprisingly, we need to add a second drop interaction. Since I am using Xcode 9, I can command + click to rename the setupDragInteraction function we created earlier to setupDragAndDropInteraction (since we will also want the image view to be the drop area for other images).

Drop Implementation

After adding the drop interaction, that method will look like this:

func setupDragAndDropInteraction() {
let dragInteraction = UIDragInteraction(delegate: self)
let dropInteraction = UIDropInteraction(delegate: self)
imageView.addInteraction(dragInteraction)
imageView.addInteraction(dropInteraction)
}

Next, we need to conform to UIDropInteractionDelegate.

In this case, we just need to specify:

  1. The class type our app can handle (UIImage).
  2. What operation the app should take with the drop (cancel local drops and copy drops from outside app).
  3. What should happen when the drop is performed (update the image view with the dropped image if the drop is from outside app).
extension ViewController: UIDropInteractionDelegate {
func dropInteraction(_ interaction: UIDropInteraction,
canHandle session: UIDropSession) -> Bool {
return session.canLoadObjects(ofClass: UIImage.self)
}

func dropInteraction(_ interaction: UIDropInteraction,
sessionDidUpdate session: UIDropSession) -> UIDropProposal {
// Cancel drag within app
let operation: UIDropOperation = session.localDragSession == nil ? .copy : .cancel
return UIDropProposal(operation: operation)
}

func dropInteraction(_ interaction: UIDropInteraction,
performDrop session: UIDropSession) {
if session.localDragSession != nil {
// Cancelled drag within app
return
}

session.loadObjects(ofClass: UIImage.self) { (images) in
guard let image = images.first as? UIImage else {
return
}
self.imageView.image = image
}
}
}
Drop Zone App

There is plenty more you can do with drag and drop. I recommend taking the time to watch some of the WWDC videos I linked to above, but this shows just how easy it is to get up and going with drag and drop in iOS 11. It is one of those features that benefits from wide adoption by developers, so take some time and add drag and drop support to your iOS 11 todos.

__

Subscribe to our Blog for more WWDC

Originally published at Gorilla Logic.

Show your support

Clapping shows how much you appreciated Gorilla Logic’s story.