Drag and Drop Tutorial for iOS
In this drag and drop tutorial you will build drag and drop support into UICollectionViews and between two separate iOS apps. By Christine Abernathy.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Drag and Drop Tutorial for iOS
30 mins
- Getting Started
- Drag and Drop Overview
- Adding Drag Support
- Adding Drop Support
- Responding to Drops
- Drag and Drop in the Same App
- Follow My Moves
- Optimizing the Drop Experience
- Using In-Memory Data
- Moving Items Across Collection Views
- Are You My App?
- Adding a Placeholder
- Multiple Data Representations
- Reading and Writing Geocaches
- Back to My App
- Adding Drag Support to a Custom View
- Adding Drop Support to a Custom View
- Where to Go From Here?
Apple introduced drag and drop functionality in iOS 11, allowing users to drag items from one screen location to another. On iPhone, drag and drop is only available within an app, whereas on iPads, it’s also available across apps. This is really handy for doing things like quickly adding images from Photos into an email.
In this tutorial, you’ll explore drag and drop by building upon CacheManager, two apps for managing geocaches:
CacheMaker organizes geocaches in a Kanban board of in-progress and completed items. CacheEditor allows users to edit the details of a geocache brought over from CacheMaker. You’ll implement these management features by adding drag and drop support to both apps.
Getting Started
Use the Download Materials button at the top or bottom of this tutorial to download the starter project. Open CacheManager.xcworkspace in Xcode and select CacheMaker as the active scheme:
Build and run CacheMaker. You should see two collection views with the first containing geocaches for in-progress work:
Try dragging a geocache from the in-progress lane to the completed lane:
Your goal in this first part of the tutorial is to get this working. Later on, you’ll unlock the ability to drag and drop geocaches to and from the CacheEditor companion app.
Take a look at the key CacheMaker files in Xcode:
- CachesDataSource.swift: Represents the data source for a collection view of geocaches.
- CachesViewController.swift: Displays the Kanban board for the geocaches.
These are the files you’ll be working with to add the desired functionality.
Drag and Drop Overview
When you drag items from a source app, a drag activity begins and the system creates a drag session. The source app sets up a drag item to represent the underlying data when the drag activity starts. Dropping the items in a destination app ends the drag activity.
Drag items are wrapped in an item provider which describes the data types the source app can deliver. When the items are dropped, the destination app asks for the items in the format it can consume.
Apple automatically supports drag and drop in text views and text fields. It also provides specialized APIs for table views and collection views. You can add drag and drop to custom views as well.
In this tutorial, you’ll explore drag and drop in collection views and custom views.
Adding Drag Support
Go to CachesDataSource.swift and add the following extension to the end of the file:
extension CachesDataSource {
func dragItems(for indexPath: IndexPath) -> [UIDragItem] {
let geocache = geocaches[indexPath.item]
let itemProvider = NSItemProvider(object: geocache.name as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
return [dragItem]
}
}
Here you create an item provider from the NSString
representation of the geocache name. You then return an array of one drag item which wraps this item provider.
Next, open CachesViewController.swift and add the following to the end of the file:
extension CachesViewController: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView,
itemsForBeginning session: UIDragSession,
at indexPath: IndexPath) -> [UIDragItem] {
let dataSource = dataSourceForCollectionView(collectionView)
return dataSource.dragItems(for: indexPath)
}
}
You adopt UICollectionViewDragDelegate
and implement the required method that’s called when a drag activity starts. Your implementation gets the data source for the collection view, then returns the corresponding drag items for the selected item.
Add the following to viewDidLoad()
after the collection view delegate assignment:
collectionView.dragDelegate = self
This makes the view controller the drag delegate.
Build and run the app. Tap and hold on a collection view cell representing a geocache. The tapped cell should rise up allowing you to drag it around:
Note that although you can drag an item around, you can’t drop it anywhere. Attempting to do so simply drops it back where it started.
Open Reminders in Split View next to CacheMaker. You should be able to drag a geocache and drop it into Reminders:
Reminders can accept the exported NSString
representation of the geocache name and use it to create a new reminder.
Now try dragging any text from Reminders into CacheMaker. Nothing happens. That’s because you haven’t added drop support to CacheMaker. You’re going to tackle this next.
Adding Drop Support
Go to CachesDataSource.swift and add the following to the CachesDataSource
extension:
func addGeocache(_ newGeocache: Geocache, at index: Int) {
geocaches.insert(newGeocache, at: index)
}
This adds a new geocache to the data source.
Switch over to CachesViewController.swift and add the following protocol extension to the end:
extension CachesViewController: UICollectionViewDropDelegate {
func collectionView(
_ collectionView: UICollectionView,
performDropWith coordinator: UICollectionViewDropCoordinator) {
// 1
let dataSource = dataSourceForCollectionView(collectionView)
// 2
let destinationIndexPath =
IndexPath(item: collectionView.numberOfItems(inSection: 0), section: 0)
// 3
let item = coordinator.items[0]
// 4
switch coordinator.proposal.operation
{
case .copy:
print("Copying...")
let itemProvider = item.dragItem.itemProvider
// 5
itemProvider.loadObject(ofClass: NSString.self) { string, error in
if let string = string as? String {
// 6
let geocache = Geocache(
name: string, summary: "Unknown", latitude: 0.0, longitude: 0.0)
// 7
dataSource.addGeocache(geocache, at: destinationIndexPath.item)
// 8
DispatchQueue.main.async {
collectionView.insertItems(at: [destinationIndexPath])
}
}
}
default:
return
}
}
}
Here you adopt the UICollectionViewDropDelegate
protocol. You then implement the required method that’s called when the user ends a drag activity. Your implementation:
- Gets the data source for the collection view.
- Sets the end of the collection view as the item drop destination.
- Selects the first drag item.
- Checks how you propose to handle the drop.
- Asynchronously fetches the dragged item’s data.
- Creates a new geocache with a name based on the incoming string data.
- Adds the new geocache to the data source.
- Inserts the new item in the collection view. You invoke this on the main thread since the data fetching completion block runs on an internal queue.
Responding to Drops
Add the following to the end of the UICollectionViewDropDelegate
extension:
func collectionView(
_ collectionView: UICollectionView,
dropSessionDidUpdate session: UIDropSession,
withDestinationIndexPath destinationIndexPath: IndexPath?
) -> UICollectionViewDropProposal {
if session.localDragSession != nil {
return UICollectionViewDropProposal(operation: .forbidden)
} else {
return UICollectionViewDropProposal(
operation: .copy,
intent: .insertAtDestinationIndexPath)
}
}
You specify the response to an item being dragged. This includes providing visual feedback to the user.
The code here forbids drag-and-drops within the app. It proposes copy operations for items dropped from another app.
Add the following to viewDidLoad()
after the drag delegate is assigned:
collectionView.dropDelegate = self
This sets the view controller as the drop delegate.
Build and run the app. With Reminders in Split View, verify that you can drag a reminder into the in-progress collection view:
If you try to drop into the middle of the list, you’ll see it only adds to the end of the list. You’ll improve this later.
Try dragging and dropping a geocache inside the app. Verify that you get a visual cue that this is disallowed:
That isn’t ideal, so you’ll work on it next.