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 http://www.raywenderlich.com/107439/uicollectionview-custom-layout-tutorial-pinterest

Bumping my question since we have the new forums :slightly_smiling:

Has anyone had any luck implementing this with photos that are downloaded asynchronously? I’m using default sizes until my photos are downloaded. HeightForPhotoAtIndexPath and HeightForAnnotationAtIndexPath are both called immediately when the collectionView loads and sizes the cells with the default sizes…but I can’t find a way to call them again when my photo downloads are completed. For example, reloading the collectionView doesn’t call them again.

2 Likes

Did you try this: collectionView.collectionViewLayout.invalidateLayout() ?

I also want to implement this with photos that are downloaded asynchronously,and I change a few codes like this.
private var cache = Dictionary<NSIndexPath, UICollectionViewLayoutAttributes>()
cache[indexPath] = attributes
and add this method
override func layoutAttributesForItemAtIndexPath(_ indexPath: NSIndexPath) → UICollectionViewLayoutAttributes? {
let attributes = cache[indexPath]
return attributes
}
still not work
See my stackoverflow question here.
Failure in append items asynchronously in collection view Thank you

great example, congratulations! I had only problem in last cell. the height is not correct, i believe it is needs to adjust width, is it correct?

i fix it, the variable cellPadding had 6, i changed for 8 but thanks for your code, excellent!

Did you find any solution for this? I am also trying to achieve the same.

Sorry for my bad English, I wish this the could help somebody running in memory issues.
Without adding this correction you’ll create a retain cycle, and the CollectionViewController (with all the image inside!) will never be deinit, consuming lot of memory!

protocol PinterestLayoutDelegate: class {…}

class PinterestLayout: UICollectionViewLayout {
// 1
weak var delegate: PinterestLayoutDelegate!

}

If this is correct please update the tutorial :slightly_smiling:

Excelente trabajo Ernesto!

Aside from congrats, I must say that in order to make it work I had to pass the delagate to the UICollectionViewLayout like so.

//PhotoStreamViewController

@IBOutlet var collView: UICollectionView!

override func viewDidLoad() {
    super.viewDidLoad()   

    let coll = self.collView.collectionViewLayout as! PinterestLayout
    coll.delegate = self
  }

In my case I am loading the collection from a server and now works great.

Muchas gracias y adelante con el gran trabajo.

Jose

Hi.
Is there any way to make the first one full width? As one column and others as two/more?
Best, Patryk

Hi, I used the concepts this tutorial in my app. The app would occasionally crash complaining that there weren’t enough attributes. According to the definition of layoutAttributesForElementsInRect() And the tutorial you’re suppose to return the attributes that are within the rect and that’s what I was doing.

I did some research and found an Apple example Collection View Transitions where it returns the entire array of cached attributes. This collection view has items beyond the size of the device so I would have expected that it return only a subset of the attributes.

I changed my app to return all cached attributes (ignoring the rect) and now everything works fine.

Any idea why, apparently sometimes, all cached attributes are needed?

Thanks.

Try ratio for image :D. That’s the key!!

I am Loading The Data From The Api For The First Time I m Getting 20 Counts in Array And the Data gets filled in the Collection View Very Perfectly.

While trying to reload data when user reaches the end of the first 20 counts the reload collection view returns 40 counts but the data doesn’t gets filled in for the second twenty please let me know if there is some prob in the class or from my side in the code.

As I Have Tried up many different solutions for Reloading the Collection View But none of them are getting Worked

i m calling the below func when users get on 19 indexpath so as i can load another data to display.

func reloadData(response:NSDictionary){

    if(response.valueForKey("success")?.boolValue == true){
        isCallable = false
        pageIndex = pageIndex + 20
        arrData.addObjectsFromArray(response.valueForKey("data") as! NSMutableArray as [AnyObject])
        NSLog("ARRAY DATA == >%@",arrData)

// dispatch_async(dispatch_get_main_queue(),{
// collectionView.reloadData()
// collectionView.layoutIfNeeded()
// });
NSOperationQueue.mainQueue().addOperationWithBlock({
self.collectionView.reloadData()

        })
    }
        
    else{

          CommonNSObject.showAlert("FAILED TO LOAD DATA")
        
    }
}

I am Loading The Data From The Api For The First Time I m Getting 20 Counts in Array And the Data gets filled in the Collection View Very Perfectly.

While trying to reload data when user reaches the end of the first 20 counts the reload collection view returns 40 counts but the data doesn’t gets filled in for the second twenty please let me know if there is some prob in the class or from my side in the code.

As I Have Tried up many different solutions for Reloading the Collection View But none of them are getting Worked

i m calling the below func when users get on 19 indexpath so as i can load another data to display.

func reloadData(response:NSDictionary){

if(response.valueForKey("success")?.boolValue == true){
    isCallable = false
    pageIndex = pageIndex + 20
    arrData.addObjectsFromArray(response.valueForKey("data") as! NSMutableArray as [AnyObject])
    NSLog("ARRAY DATA == >%@",arrData)

// dispatch_async(dispatch_get_main_queue(),{
// collectionView.reloadData()
// collectionView.layoutIfNeeded()
// });
NSOperationQueue.mainQueue().addOperationWithBlock({
self.collectionView.reloadData()

    })
}

else{

      CommonNSObject.showAlert("FAILED TO LOAD DATA")
    
}

}

Hello,

Is it possible to match the height for the rows ?
So for example if let’s say column 1 calculated height is 50px, and column 2 is 60, I want to keep them both at 60, and add padding to column 1.
I tried to do this with auto layout but I couldn’t get the first column to match the height the second one.

Hello, thanks for great tutorial! It just works perfectly.

The only thing that I noticed when the number of cells is changed, it crashes.
I have tried to debug and sow that the problem was the array of caches which will cache also the UICollectionViewLayoutAttributes.

My solution was to add:

@interface PinterestLayout : UICollectionViewLayout

- (void)invalidateLayout
{
[self.cache removeAllObjects];
[super invalidateLayout];
}

@end

// call to reload the collection view
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView reloadData];

Sorry for Objective-C :slight_smile:

Has anybody been able to use this pinterest layout with async api calls? Like Alamofire? I’ve been having trouble when it reloads the data. And also the cells that are supposed to load after the second pagination is not returning back. Looks like alot of people are having a similar problem with async calls and using this layout. I think we should have the OP chime in on this issue with some support. Would really appreciate the help if we knew why this isn’t working.

Could this be something wrong with the datasoure?

Cheers!

2 Likes

For me same issue can any one help me out

Does anyone know what the class UIImage+Decompression.swift is doing?

extension UIImage {
var decompressedImage: UIImage {
UIGraphicsBeginImageContextWithOptions(size, true, 0)
drawAtPoint(CGPointZero)
let decompressedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return decompressedImage
}
}

It looks like we are redrawing the image, and the only thing we’re doing is specifying that there’s no transparency and no scaling. Can anyone tell me if/why this was required in the project?

Would be interested to know on how to use this layout where I mage is downloaded from web. Do I need to call invalidate layout?
Invalidate Layout

1 Like