UICollectionView Tutorial: Headers, Selection and Reordering

Learn how to implement reusable views for UICollectionView section headers, select cells and reorder with drag and drop. By Fabrizio Brancati.

4.7 (7) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Implementing Drag Interactions

Click File ▸ New ▸ File and select Swift File. Click Next, name it FlickrPhotosViewController+DragAndDropDelegate and click Create.

Replace the contents of the file with the following:

import UIKit

// MARK: - UICollectionViewDragDelegate
extension FlickrPhotosViewController: UICollectionViewDragDelegate {
  func collectionView(
    _ collectionView: UICollectionView,
    itemsForBeginning session: UIDragSession,
    at indexPath: IndexPath
  ) -> [UIDragItem] {
    // 1
    let flickrPhoto = photo(for: indexPath)
    guard let thumbnail = flickrPhoto.thumbnail else {
      return []
    }

    // 2
    let item = NSItemProvider(object: thumbnail)

    // 3
    let dragItem = UIDragItem(itemProvider: item)

    // 4
    return [dragItem]
  }
}

collectionView(_:itemsForBeginning:at:) is the only required method for this protocol, and it’s also the only one you need for this feature. Here’s what it does:

  1. First, you fetch the thumbnail of the selected photo from the photo object.
  2. Create an NSItemProvider object referencing the thumbnail. This is used by the drag system to indicate what item is being dragged.
  3. Then, create a UIDragItem that represents the selected item to be dragged.
  4. The method must return an array of UIDragItem objects, so you add the dragItem created above in an array.

Next, you need to let the collection view know it can handle drag interactions. Open FlickrPhotosViewController.swift and implement viewDidLoad() above shareButtonTapped(_:):

override func viewDidLoad() {
  super.viewDidLoad()
  collectionView.dragInteractionEnabled = true
  collectionView.dragDelegate = self
}

In this method, you set the collectionView’s dragInteractionEnabled property to true and set the dragDelegate.

Now’s a great time to build and run. Perform a search, and now you’ll be able to long-press a cell to see the drag interaction. You can see it lift and can drag it around. But, you won’t be able to drop it anywhere just yet.

Photo being dragged

Next up, it’s time to implement drop behavior.

Implementing Drop Interactions

Open FlickrPhotosViewController+DragAndDropDelegate.swift. Now, you need to implement some methods from UICollectionViewDropDelegate to enable the collection view to accept dropped items from a drag session. This will also allow you to reorder the cells by taking advantage of the provided index paths from the drop methods.

Create another extension to conform to UICollectionViewDropDelegate:

extension FlickrPhotosViewController: UICollectionViewDropDelegate {
  func collectionView(
    _ collectionView: UICollectionView, 
    canHandle session: UIDropSession
  ) -> Bool {
    return true
  }
}

Typically in this method, you would inspect the proposed drop items and decide whether you wanted to accept them. Because you’re enabling drag-and-drop for only one item type in this app, you simply return true here. Don’t worry about the compiler error; you’ll fix it shortly.

To update the source data array with changes, you need to make it mutable. Open FlickrSearchResults.swift and update searchResults as follows:

var searchResults: [FlickrPhoto]

Before you implement the next method, open FlickrPhotosViewController+Helper.swift and place these methods right under photo(for:):

func removePhoto(at indexPath: IndexPath) {
  searches[indexPath.section].searchResults.remove(at: indexPath.row)
}

func insertPhoto(_ flickrPhoto: FlickrPhoto, at indexPath: IndexPath) {
  searches[indexPath.section].searchResults.insert(
    flickrPhoto, 
    at: indexPath.row)
}

These methods allow removing and inserting photos into the searchResults array.

Making the Drop

Open FlickrPhotosViewController+DragAndDropDelegate.swift and implement the following method inside the UICollectionViewDropDelegate extension:

func collectionView(
  _ collectionView: UICollectionView,
  performDropWith coordinator: UICollectionViewDropCoordinator
) {
  // 1
  guard let destinationIndexPath = coordinator.destinationIndexPath else {
    return
  }

  // 2
  coordinator.items.forEach { dropItem in
    guard let sourceIndexPath = dropItem.sourceIndexPath else {
      return
    }

    // 3
    collectionView.performBatchUpdates({
      let image = photo(for: sourceIndexPath)
      removePhoto(at: sourceIndexPath)
      insertPhoto(image, at: destinationIndexPath)
      collectionView.deleteItems(at: [sourceIndexPath])
      collectionView.insertItems(at: [destinationIndexPath])
    }, completion: { _ in
      // 4
      coordinator.drop(
        dropItem.dragItem,
        toItemAt: destinationIndexPath)
    })
  }
}

This delegate method accepts the drop items and performs maintenance on the collection view and the underlying data storage array to properly reorder the dropped items. You’ll see UICollectionViewDropCoordinator referred to here. This UIKit class gives you more information about the proposed drop items.

Here’s what happens, in detail:

  1. Get destinationIndexPath from the drop coordinator.
  2. Loop through the items — an array of UICollectionViewDropItem objects — and ensure each has a sourceIndexPath.
  3. Perform batch updates on the collection view, removing items from the array at the source index and inserting them in the destination index. After completing that, perform deletes and updates on the collection view cells.
  4. In the completion handler, perform the drop action.

Open FlickrPhotosViewController.swift, find viewDidLoad() and add the following code to the end of the method:

collectionView.dropDelegate = self

You simply set the dropDelegate.

You’re doing great! Just the last step to go now! Open FlickrPhotosViewController+DragAndDropDelegate.swift and implement the following method at the bottom of the UICollectionViewDropDelegate extension:

func collectionView(
  _ collectionView: UICollectionView,
  dropSessionDidUpdate session: UIDropSession,
  withDestinationIndexPath destinationIndexPath: IndexPath?
) -> UICollectionViewDropProposal {
  return UICollectionViewDropProposal(
    operation: .move,
    intent: .insertAtDestinationIndexPath)
}

The UIKit drop session calls this method constantly during interaction to interpolate where the user is dragging the items and to give other objects a chance to react to the session.

This delegate method causes the collection view to respond to the drop session with UICollectionViewDropProposal that indicates the drop will move items and insert them at the destination index path.

Great! It’s time to build and run.

Drop action

You now see a drag session coupled with some nice behavior in your collection view. As you drag your item over the screen, other items shift to move out of the way, indicating the drop can be accepted at that position. You can even reorder items between different search sections!

Where to Go From Here?

Congratulations! You’ve just finished a whirlwind tour of some pretty advanced UICollectionView features. :]

Along the way, you learned how to delineate sections with reusable headers, select cells, update cell layout, perform multiple selections, reorder items and much more. You can download the final project by clicking the Download Materials button at the top or bottom of this tutorial.

You can also find an interesting video course about UICollectionView at Collection Views.

If you have any questions or comments about UICollectionView or this tutorial, please join the forum discussion below.