Kodeco Forums

UICollectionView Tutorial: Reusable Views, Selection, and Reordering

In this UICollectionView tutorial, you'll power up your collection view skills by learning about cell selection, cell reordering, and supplementary views.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/1404-uicollectionview-tutorial-reusable-views-selection-and-reordering
1 Like

Nice tutorial, thanks. A very welcome follow-up would be a tutorial on how to hook these up to an NSFetchedResultsController. No one ever seems to cover this aspect properly. As great a tool as they are for data display, not explaining how to connect them to iOSโ€™s native persistent data store always seems a bit odd.

In collection view moveItemToIndexPath, how to add feedback animation when we do long press on image? such as shake animation

Hello,

After updating Swift 3. I am living a few problem. How can I update func loadLargeImage() for Swift 3.

thanks

Hey @durul,

This tutorial has now been updated to use Xcode 8, Swift 3, and iOS 10. Please let me know if you run into any further issues.

Hey @micpringle thank you.

Hello, thanks for the tutorial!

@micpringle The green border is only showing on the left and the top, can you help with that?

Thanks

Great Tutorial Thanks. There are some Swift 3 updates that are missing:

  • The UICollectionViewDataSource method:

      override func collectionView(_ collectionView: UICollectionView,
                           moveItemAtIndexPath sourceIndexPath: IndexPath,
                           to destinationIndexPath: IndexPath) {
    

    should be:

      override func collectionView(_ collectionView: UICollectionView,
                                   moveItemAt sourceIndexPath: IndexPath,
                                   to destinationIndexPath: IndexPath) {
    

Also the method:

override func collectionView(collectionView: UICollectionView,
                                 didDeselectItemAtIndexPath indexPath: NSIndexPath) {

should be:

override func collectionView(_ collectionView: UICollectionView,
                                 didDeselectItemAt indexPath: IndexPath) {

And

var flickrPhoto = photoForIndexPath(indexPath)

Should be:

let flickrPhoto = photoForIndexPath(indexPath)

And at the end there is a bug when you move an image to a different section. That case was not taken in to account and that makes the App crash.

But is a great tutorial to learn about Collection views. Thanks again.

I believe that this code is wrong:

// New code
if indexPath == largePhotoIndexPath {
  let flickrPhoto = photoForIndexPath(indexPath)
  var size = collectionView.bounds.size
  size.height -= topLayoutGuide.length
  size.height -= (sectionInsets.top + sectionInsets.right)
  size.width -= (sectionInsets.left + sectionInsets.right)
  return flickrPhoto.sizeToFillWidthOfSize(size)
}

Specifically the line:
size.height -= (sectionInsets.top + sectionInsets.right)

I think it should be:
size.height -= (sectionInsets.top + sectionInsets.bottom)

The function:

override func collectionView(_ collectionView: UICollectionView,
                             moveItemAtIndexPath sourceIndexPath: IndexPath,
                             to destinationIndexPath: IndexPath) {
 
  var sourceResults = searches[(sourceIndexPath as NSIndexPath).section].searchResults
  let flickrPhoto = sourceResults.remove(at: (sourceIndexPath as NSIndexPath).row)
 
  var destinationResults = searches[(destinationIndexPath as NSIndexPath).section].searchResults
  destinationResults.insert(flickrPhoto, at: (destinationIndexPath as NSIndexPath).row)  
}

would have no effect on the actual seaches array. This is all code that lives within the function but makes no difference to anything. It should be changed to something like this:

override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
	let sourceSection = searches[sourceIndexPath.section]
	var newSearchResults = sourceSection.searchResults
	let movedPhoto = newSearchResults.remove(at: sourceIndexPath.row)
	searches[sourceIndexPath.section] = FlickrSearchResults(searchTerm: sourceSection.searchTerm, searchResults: newSearchResults)
		
	let destinationSection = searches[destinationIndexPath.section]
	newSearchResults = destinationSection.searchResults
	newSearchResults.insert(movedPhoto, at: destinationIndexPath.row)
	searches[destinationIndexPath.section] = FlickrSearchResults(searchTerm: destinationSection.searchTerm, searchResults: newSearchResults)
}
2 Likes

Great tutorial. Thereโ€™s an error in the tutorial which is not duplicated in the final project code.

var largePhotoIndexPath: NSIndexPath?

should be:

var largePhotoIndexPath: IndexPath?

Probably got missed when it was converted to Swift 3.

Thanks for the great tutorial. How would you implement a long press detection on a cell?
The following checks for the selection. What if you wanted to check for a long press ?
Thank you

override func collectionView(_ collectionView: UICollectionView,
didDeselectItemAt indexPath: IndexPath) {

    guard sharing else {
        return
    }
    
    let photo = photoForIndexPath(indexPath)
    
    if let index = selectedPhotos.index(of: photo) {
        selectedPhotos.remove(at: index)
        updateSharedPhotoCount()
    }
}

Excellent tutorial and very helpful! Thank you. I converted it to Swift 4 and IOS 10. And thanks for the correction to moveItemAt. For example, the share bar button action could be:

@IBAction func share(_ sender: UIBarButtonItem) {

    // We have no photos
    guard !searches.isEmpty
    else {  return  }

    // We have not selected photos
    guard !selectedPhotos.isEmpty
    else {  sharing = !sharing; return }

    if sharing {
        let imageArray: [UIImage] = selectedPhotos.map {  return $0.thumbnail!  }

        if !imageArray.isEmpty {
            let shareScreen = UIActivityViewController(activityItems: imageArray, applicationActivities: nil)
            shareScreen.completionWithItemsHandler = {
                [unowned self] (activityType, completed, returnedItems, activityError) in
                self.sharing = false
            }

            let popoverPresentationController = shareScreen.popoverPresentationController
            popoverPresentationController?.barButtonItem = sender
            popoverPresentationController?.permittedArrowDirections = .any
            present(shareScreen, animated: true, completion: nil)
        }
    }
}

This works as well:

    var sourceResults = searches[sourceIndexPath.section].searchResults
    let flickrPhoto = sourceResults.remove(at: sourceIndexPath.row)
    searches[sourceIndexPath.section].searchResults = sourceResults

    var destinationResults = searches[destinationIndexPath.section].searchResults
    destinationResults.insert(flickrPhoto, at: destinationIndexPath.row)
    searches[destinationIndexPath.section].searchResults = destinationResults

Sorry, that was for correcting the collectionView(_:moveItemAt:to:) UICollectionViewDataSource method. Iโ€™ll post it there.

This works too:

var sourceResults = searches[sourceIndexPath.section].searchResults
let flickrPhoto = sourceResults.remove(at: sourceIndexPath.row)
searches[sourceIndexPath.section].searchResults = sourceResults

var destinationResults = searches[destinationIndexPath.section].searchResults
destinationResults.insert(flickrPhoto, at: destinationIndexPath.row)
searches[destinationIndexPath.section].searchResults = destinationResults

This line failed to compile, because the searchResults member is a let instead of var.
searches[sourceIndexPath.section].searchResults = sourceResults

This tutorial is more than six months old so questions are no longer supported at the moment for it. We will update it as soon as possible. Thank you! :]

1 Like