Chapters

Hide chapters

Catalyst by Tutorials

First Edition · iOS 13 · Swift 5.1 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

3. Drag & Drop
Written by Marin Bencevic

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

On macOS, dragging and dropping files is such an integral feature that we typically just take its presence for granted. It’s so useful that Apple decided to bring it over to the iPad in iOS 11. With Catalyst, you can go full circle and bring the iPad’s drag and drop back to macOS. Everything old is new again, right?

In this chapter, you’ll update the app you’ve been working on so your users can add new photos to their diary entry by dragging them from other apps and dropping them into the entry view. You’ll also update the entry photos collection view to allow reordering the photos by dragging and dropping.

How do drag and drop work?

Before you start working, you need to get familiar with the basics of drag and drop. I know theory can be a drag, but bear with me here.

Each drag and drop action has two sides: The dragging side and the dropping side. The objects on both of these sides are UIViews. To enable dragging, you need to add a drag interaction to a view. When iOS detects that a drag is being attempted on a view, the drag interaction uses a drag interaction delegate to determine which drag items will be dragged. This delegate can also customize the look of these items as they’re being dragged.

The whole point of drag and drop is to pass around data. The drag items being passed around are not the actual data. Instead, they contain a way to get the data once the drop happens.

On the other side, a drop interaction uses a drop interaction delegate to determine what will happen when the item is dropped into the destination view. The drop interaction delegate can also animate the destination view whenever the dragged items enter the destination view or change position inside the view.

Overseeing all of this is a drop session. Drop session is a manager, but the good kind of manager: It lets you work without getting in your way, but always has answers ready to any pressing questions you might have. The session knows which items are being dragged, how to get their contents, where they’re being dropped to and other useful contextual information.

To enable drag and drop on your views, all you need to do is add the interactions and implement the delegate methods. Once you do that for your iPad app, it will work on macOS automatically. It also works on iOS, but only inside your app.

OK, that’s the theory. Let’s drop right in and get into some code!

Dropping new photos

Open the starter project from the provided materials and run it on macOS. Take a look at the entry photos collection view. Currently, you can only add new photos by clicking on the camera icon and choosing a photo. On macOS, it’s common to select a bunch of photos from the Finder or the Photos app and just drop them into the destination app. Let’s make that happen.

let interaction = UIDropInteraction(delegate: self)
textView.interactions.append(interaction)
// MARK: - UIDropInteractionDelegate
extension EntryTableViewController: 
  UIDropInteractionDelegate {
  
  func dropInteraction(
    _ interaction: UIDropInteraction, 
    canHandle session: UIDropSession) -> Bool {
    
    session.canLoadObjects(ofClass: UIImage.self)
  }

}
func dropInteraction(
  _ interaction: UIDropInteraction, 
  sessionDidUpdate session: UIDropSession) -> UIDropProposal {
  
  UIDropProposal(operation: .copy)
}
func dropInteraction(
  _ interaction: UIDropInteraction, 
  performDrop session: UIDropSession) {

  session.loadObjects(ofClass: UIImage.self) {
    [weak self] imageItems in
    guard let self = self else { return }

  }
}
let images = imageItems as! [UIImage]
self.collectionView.performBatchUpdates({
  self.entry?.images.insert(contentsOf: images, at: 0)
  let indexPaths = Array(
    repeating: IndexPath(item: 0, section: 0), 
    count: images.count)
  self.collectionView.insertItems(at: indexPaths)
})

Dropping inside UICollectionView

Dropping to the text view is convenient when you want to add a bunch of photos to the entry, but it would be nice to let the users drag photos into specific positions directly inside the collection view. You’ll also show an animation of the existing photos parting to allow space for the newly dropped photos.

collectionView.dropDelegate = self
// MARK: - UICollectionViewDropDelegate
extension EntryTableViewController: 
  UICollectionViewDropDelegate {
  
  func collectionView(
    _ collectionView: UICollectionView, 
    canHandle session: UIDropSession) -> Bool {
    
    session.canLoadObjects(ofClass: UIImage.self)
  }
}
func collectionView(
  _ collectionView: UICollectionView, 
  dropSessionDidUpdate session: UIDropSession, 
  withDestinationIndexPath destinationIndexPath: IndexPath?) 
  -> UICollectionViewDropProposal {
    
  UICollectionViewDropProposal(
    operation: .copy, 
    intent: .insertAtDestinationIndexPath)
}
func collectionView(
  _ collectionView: UICollectionView, 
  performDropWith coordinator: 
  UICollectionViewDropCoordinator) {
  
  let destinationIndex = coordinator.destinationIndexPath ??
    IndexPath(item: 0, section: 0)
  
}
// 1
coordinator.session.loadObjects(ofClass: UIImage.self) {
  [weak self] imageItems in
  
  guard let self = self else { return }
  let images = imageItems as! [UIImage]
  
  // 2
  self.entry?.images.insert(
    contentsOf: images,
    at: destinationIndex.item)
  
  // 3
  self.collectionView.performBatchUpdates({
    let newIndexPaths = Array(
      repeating: destinationIndex,
      count: images.count)
    self.collectionView.insertItems(at: newIndexPaths)
  })
}

Reordering collection view items

Speaking of the work UIKit does for you, if you implement both dragging and dropping for a collection or table view, it will automatically support reordering the elements. That’s why your next task is adding drag support to the collection view. By the end of this section, you’ll be a real drag queen!

collectionView.dragDelegate = self
// MARK: - UICollectionViewDragDelegate
extension EntryTableViewController: 
  UICollectionViewDragDelegate {
  
  func collectionView(
    _ collectionView: UICollectionView, 
    itemsForBeginning session: UIDragSession, 
    at indexPath: IndexPath) -> [UIDragItem] {
    
    guard let entry = entry, !entry.images.isEmpty else {
      return []
    }
    
    let image = entry.images[indexPath.item]
    let provider = NSItemProvider(object: image)
    return [UIDragItem(itemProvider: provider)]
  }
}
if session.localDragSession != nil {
  return UICollectionViewDropProposal(
        operation: .move, 
        intent: .insertAtDestinationIndexPath)
} else {
  return UICollectionViewDropProposal(
        operation: .copy, 
        intent: .insertAtDestinationIndexPath)
}
if coordinator.session.localDragSession != nil {
  collectionView.performBatchUpdates({
    for item in coordinator.items {
      guard let sourceIndex = item.sourceIndexPath else {
        return
      }
      
      self.entry?.images.remove(at: sourceIndex.item)
      self.collectionView.deleteItems(at: [sourceIndex])
    }
  })
}

Key points

  • Add drag and drop to custom views by adding a drag or drop interaction to the view and implementing the necessary delegate methods.
  • Use a drag session to get the dragged items and load the dragged data.
  • For table and collection views, it’s enough to implement the collection or table view specific drop and drag delegate methods.
  • An iPadOS implementation of drag and drop works on macOS and iOS, but on iOS, it only works within one app.

Where to go from here?

In this chapter, you implemented a basic implementation of drag and drop. You can go even further by including custom animations while the user is dragging items across your views. You can also customize how item previews look while they’re being dragged. You can see an implementation of that in the WWDC 2017 session called Mastering Drag and Drop which you can find here: https://apple.co/2vOhvYA.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now