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.
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
UICollectionView Tutorial: Headers, Selection and Reordering
30 mins
- Getting Started
- Adding Custom Section Headers
- Creating a UICollectionReusableView Class
- Setting the Section Header View Class
- Connecting the Section Header to Data
- Interacting With Cells
- Providing Selection Feedback
- Loading the Large Image
- Selecting Multiple Cells
- Keeping Track of Sharing
- Adding a Share Button
- Reordering Cells
- Implementing Drag Interactions
- Implementing Drop Interactions
- Making the Drop
- Where to Go From Here?
Selecting Multiple Cells
You’ll add a feature where you can select multiple images and then share them. You’ll collect selected images in an array and then use a native share sheet to allow sharing out via any available method on the device.
Open FlickrPhotosViewController.swift and add the following at the top of FlickrPhotosViewController
.
var selectedPhotos: [FlickrPhoto] = []
let shareTextLabel = UILabel()
var isSharing = false
Here’s what each of these is for:
- selectedPhotos: keeps track of all currently selected photos while in sharing mode.
- shareTextLabel: gives the user feedback about how many photos are currently selected.
- isSharing: used to track whether the view controller is in sharing mode.
Next, open FlickrPhotosViewController+Helper.swift and add the following method to the extension:
func updateSharedPhotoCountLabel() {
if isSharing {
shareTextLabel.text = "\(selectedPhotos.count) photos selected"
} else {
shareTextLabel.text = ""
}
shareTextLabel.textColor = themeColor
UIView.animate(withDuration: 0.3) {
self.shareTextLabel.sizeToFit()
}
}
You’ll call this method to keep shareTextLabel
up to date. This method checks isSharing
. If the app is currently in sharing mode, it sets the label text and animate the size change to fit along with all the other elements in the navigation bar.
That handles the state behind sharing. Now, you need to add a way for the user to enter sharing mode.
Keeping Track of Sharing
Open FlickrPhotosViewController.swift again and replace the isSharing
stored property with the following computed property below largePhotoIndexPath
:
var isSharing = false {
didSet {
// 1
collectionView.allowsMultipleSelection = isSharing
// 2
collectionView.selectItem(at: nil, animated: true, scrollPosition: [])
selectedPhotos.removeAll()
guard let shareButton = navigationItem.rightBarButtonItems?.first else {
return
}
// 3
guard isSharing else {
navigationItem.setRightBarButtonItems([shareButton], animated: true)
return
}
// 4
if largePhotoIndexPath != nil {
largePhotoIndexPath = nil
}
// 5
updateSharedPhotoCountLabel()
// 6
let sharingItem = UIBarButtonItem(customView: shareTextLabel)
let items: [UIBarButtonItem] = [
shareButton,
sharingItem
]
navigationItem.setRightBarButtonItems(items, animated: true)
}
}
After the property is set, here’s what happens:
- Set the
allowsMultipleSelection
property of the collection view. If you’re in sharing mode, then you need to be able to select multiple cells. - Deselect all cells, scroll to the top and remove any existing items from the array of selected photos.
- If sharing is not enabled, set the share button to the default state and return.
- Make sure
largePhotoIndexPath
is set tonil
as you don’t want any previously selected cell to still be shown large. - Call the convenience method you created above to update the share label.
- Update the bar button items to show the label alongside the share button.
Adding a Share Button
You now need to add the share button to the UI. First open Main.storyboard, open the object library with Command-Shift-L and drag a Bar Button Item onto the FlickrPhotosViewController
to the right of the search bar in the navigation bar. Open the Attributes inspector and set the System Item to Action.
Then open FlickrPhotosViewController.swift in an additional editor pane, and Control-drag from the share button to the bottom of the FlickrPhotosViewController
class. Create an Action named shareButtonTapped and set the Type to UIBarButtonItem.
Fill in the method as follows:
// 1
guard !searches.isEmpty else {
return
}
// 2
guard !selectedPhotos.isEmpty else {
isSharing.toggle()
return
}
// 3
guard isSharing else {
return
}
// TODO: Add photo sharing logic!
This method:
- Checks if there are any searches. If not, then there are no photos to share so simply return.
- If there are no selected photos currently then toggle sharing on or off.
- Simply return if in sharing mode.
Now open FlickrPhotosViewController+UICollectionViewDelegate.swift and add the following to the top of collectionView(_:shouldSelectItemAt:)
:
guard !isSharing else {
return true
}
This method is used to handle single selection when not in sharing mode. So, if the app is in sharing mode, you want the method to do nothing.
Next, add the following method below collectionView(_:shouldSelectItemAt:)
:
override func collectionView(
_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath
) {
guard isSharing else {
return
}
let flickrPhoto = photo(for: indexPath)
selectedPhotos.append(flickrPhoto)
updateSharedPhotoCountLabel()
}
This responds to an item being selected in the collection view but is called after the item is selected. Here you add the selected photo to the sharedPhotos
array and update the shareTextLabel
text.
Next, add the following underneath the method you just added:
override func collectionView(
_ collectionView: UICollectionView,
didDeselectItemAt indexPath: IndexPath
) {
guard isSharing else {
return
}
let flickrPhoto = photo(for: indexPath)
if let index = selectedPhotos.firstIndex(of: flickrPhoto) {
selectedPhotos.remove(at: index)
updateSharedPhotoCountLabel()
}
}
This removes an item from selectedPhotos
and updates the label if the user taps a selected cell to deselect it.
Finally, open FlickrPhotosViewController again and find the // TODO
comment in shareButtonTapped(_:)
and replace it with this:
// 1
let images: [UIImage] = selectedPhotos.compactMap { photo in
guard let thumbnail = photo.thumbnail else {
return nil
}
return thumbnail
}
// 2
guard !images.isEmpty else {
return
}
// 3
let shareController = UIActivityViewController(
activityItems: images,
applicationActivities: nil)
// 4
shareController.completionWithItemsHandler = { _, _, _, _ in
self.isSharing = false
self.selectedPhotos.removeAll()
self.updateSharedPhotoCountLabel()
}
// 5
shareController.popoverPresentationController?.barButtonItem = sender
shareController.popoverPresentationController?.permittedArrowDirections = .any
present(shareController, animated: true, completion: nil)
Here’s what that does:
- Create an array of images containing thumbnails of photos selected by the user.
- Ensure the selected images array is not empty.
- Create a native
UIActivityViewController
and pass the images array in theactivityItems
parameter. - Define the completion handler to update properties after the user performs a share action.
- Present the activity view controller over the current view controller. iOS presents any system apps or services that can handle a list of images.
Once again, check your work! Build and run, perform a search and tap the share button in the navigation bar. Select multiple images and watch the label update as you select new cells:
Tap the share button again, and the native share sheet will appear. If you’re on a device, you can select any app or service that accepts images to share:
Congratulations! :]
Reordering Cells
In this last segment, you’ll learn how to use the native drag-and-drop feature to reorder cells in the collection view. iOS has some nice convenience methods built-in for UICollectionView
, which take advantage of the drag-and-drop features that iOS 11 added.
To use drag and drop, you have to be aware of two protocols. UICollectionViewDragDelegate
drives drag interactions and UICollectionViewDropDelegate
drives drop interactions. You’ll implement the drag delegate first, then test behavior and finally complete this feature with a drop delegate.