Kodeco Forums

UICollectionView Custom Layout Tutorial: Pinterest

In this tutorial, you'll build a UICollectionView custom layout inspired by the Pinterest app, including how to cache attributes and dynamically size cells.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/392-uicollectionview-custom-layout-tutorial-pinterest

Too funny, I spent all day yesterday trying to get this tutorial to work with no success and you just update it today. Thanks! I’ll give it another shot.

HI,

I have checked the tutorial & make changes in PinterestLayout.swift file for sorting cells as per height instead of regular sorting.

Made below changes in prepareLayout() method

var offset:(x:CGFloat,y:CGFloat){
    guard let min = yOffset.min() ,let index = yOffset.index(of: min), min != 0 else{
      return (xOffset[column],yOffset[column])
    }
    yOffset[index] = min
    column = index
    return (xOffset[index],min)
  }
  
  let frame = CGRect(x: offset.x, y: offset.y, width: columnWidth, height: height)

I hope you will understand what i mean!

Awesome tutorial! Thanks for making this tutorial so simple!

Thanks for the iOS 11 tutorial, but can’t this be done with auto layout? For example a stack view and using the built in flow layout? I’m asking because I have tried this approach and it isn’t working for me but I’m not sure why. It is surprising that one needs to go to the trouble of coding a custom layout class for a fairly simple task.

If I am downloading images from web and using it in the cell, Is this logic of height of photo used for cell height gonna work the same way, as cell don’t have image height at the creation of the cell.

Nice tutorial! However a very interesting question of @buntsem. Exactly the thing I am facing. I download the image from a cloud backend and show it in the cell. the download executed after creation of the cell. Any suggestions?

Hi. Thanks so much for such a great tutorial. Question - the video series that kind of goes along with this (minus the Pinterest-style layout) uses a Master-Detail app format while this is just in a single view. What is the best approach for taking this Pinterest-style customization and embedding it in a Master-Detail design? Thanks in advance!

Hello everyone,
what if I have to delete collectionViewItems ?
Thanks

It should be mentioned that size that we return in
collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath:IndexPath) → CGFloat
takes care only of photo size and ignores size of the annotation.
This results in photo cropping (change “Comment” for some photo in Photos.plist to a long one and you will understand what i mean)
It would be great to have a fully-autolayouted collection view, that makes a cell height equal to image height PLUS annotation height. Any ideas?

@prdbrg Do you have any feedback regarding this? Thank you - much appreciated! :]

Thanks for the tutorial. There is a bug that I couldn’t figure out how to resolve. The app will crash whenever I try to add a new cell to the collection view.

 override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return cache[indexPath.item]
 }

the above function will crash indicating the out of bound exception.
what should I do to resolve it. I tried adding to the cache the moment I add a cell to the collection view. This would work if I haven’t scrolled to the end of the collection view. But If I scroll to the end of the collection view and do the adding, it will crash with the exception that I am providing a UICollectionViewLayoutAttributes for a cell that doesn’t exist.

Any help would be appreciated.

Thanks

i am facing issue when i try to reload collection view.

I have implemented this and working fine. But when i fetch the updated data from server and call reloaddata( ) on collection view. the following function
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) → UICollectionViewCell{
}
dose not get called enough number of times and ends up showing same data as before, its not creating the new cells for the new data.
where following function is returning the correct and updated count depending on my updated data.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) → Int {
}

need help.

Hello,
I understand your point of view, but for the moment subclassing the UICollectionViewLayout class is the only way to achieve a Pinterest like collection view. For sure, you can go for a stackView too, but you’ll lose cells recycling and basically the collectionView in itself. So I would say that the final choice is up to you and it depends on your use cases and needs. Don’t be afraid, here on RW there are some awesome tutorials on how to properly implement custom collection view layouts.

For exemple this one:

https://www.raywenderlich.com/107687/uicollectionview-custom-layout-tutorial-spinning-wheel

Hi,
To calculate cells positions and sizes you should know the image dimensions in advance or at least the image ratio (in order to infer the frame size). So either your backend can provide you with these informations or you have to adopt a prefetch strategy in order to get the images before your collection view is displayed. Another way could be to opt for Async Display Kit like the original Pinterest app does. ASDK is a framework maintained by Fecebook (and Pinterest too) which aims to port UIKit elements and layout engine into a fully asynchronous environment, in order to leverage prefetching and async layout calculations.

Hi,
you’re right. The scope of this tutorial was just to show how to implement a collection view layout in which each cell has a custom size, so I kept things really simple to focus on the main purpose. Anyway the same logic could be applied to annotation labels.

A good way to achieve what you need is to add another method to the PinterestLayoutDelegate called

collectionView(_ collectionView: UICollectionView, heightForAnnotationAtIndexPath indexPath:IndexPath) → CGFloat

In this method you calculate the height for the annotation and return it to the Layout object during the prepare phase. When your layout will create the attributes it will simply take in account the height of the annotation too.

Hi,
this tutorial is just an exemple on how to customize a collection view layout. So I’m sorry, but not all the specific cases are handled (landscape mode for exemple, insertion/deletion, etc)

Anyway to answer your question I’m pretty sure your crash comes from this line:

guard cache.isEmpty == true, let collectionView = collectionView else {
return
}

Try to think about the layout flow… The cache array is calculated the first time the collectionView ask for the layout. Then you modify the data set by erasing an element. At this point you should recalculate the layout by performing the prepare() routine (in order to synchronise your data set with the cell attributes). But the cache is not empty (because it was already calculated once), so the prepare routine is not fully performed.

To solve this issue you just have to add a purge cache policy to your implementation. Exemple: whenever you modify the data you should also purge the cache in order to let the prepare() method fully execute its routine.

A good place where to start is the Apple Documentation concerning how to (and when) invalidate the collection view layout.

check my answer to @gxsingh :]

How to add purge cache policy in the implementation? At what step and how to implement that?

How can I expand on this to make the cells be clickable/tappable? And clicking would take you to a page with more details on that cell/pin?

Thanks!