26

I need to know when reloading a UICollectionView has completed in order to configure cells afterwards (because I am not the data source for the cells - other wise would have done it already...)

I've tried code such as

[self.collectionView reloadData];
[self configure cells]; // BOOM! cells are nil

I've also tried using

[self.collectionView performBatchUpdates:^{
  [self.collectionView reloadData];
    } completion:^(BOOL finished) {
        // notify that completed and do the configuration now
  }];

but when I reload the data I am getting crashes.

How can I reload the data into the collection, and only when it has finished reloading - do a particular completion handler

zx81
  • 41,100
  • 9
  • 89
  • 105
Avba
  • 14,822
  • 20
  • 92
  • 192
  • I think I have encountered simmilar issue. The problem is that cells are loaded during layoutSubview which takes place during next run loop pass. Unfortunately I didn't find solution yet. I wonder why UICollectionViewDelegate doesn't have methods like: - collectionView:willBeginDisplayingCell:forItemAtIndexPath: while there is: – collectionView:didEndDisplayingCell:forItemAtIndexPath: Did you solve that one already? – lukewar Aug 20 '13 at 17:30
  • What's happening is that the batch updates block expects you to make calls to add and remove row/sections that correspond with the new number of rows and sections that occurs during the reload. If this does not occur, an assertion is thrown. – Scott Ahten Jan 03 '15 at 21:48

3 Answers3

101

This is caused by cells being added during layoutSubviews not at reloadData. Since layoutSubviews is performed during next run loop pass after reloadData your cells are empty. Try doing this:

[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
[self configure cells]; 

I had similar issue and resolved it this way.

lukewar
  • 2,715
  • 3
  • 19
  • 16
  • 1
    I had this exact problem where the `visibleCells` was 0 after calling `-reloadData`. Calling `-layoutIfNeeded` fixed this. lukewar's answer should be accepted. – Paulo Fierro Jun 26 '14 at 22:30
  • 1
    I would like to add that this solution is extremely useful in forcing the collection view to reload synchronously, incase you are making async updates from core data, which resolves huge headaches and crashes. – Mazyod Jun 01 '15 at 13:32
  • 2
    You should call `setNeedsLayout` and then `layoutIfNeeded` if you want to guarantee a layout cycle. – TheCodingArt Feb 01 '17 at 19:49
  • but why cells are loading on those cycles, I never had these issues with a lot of UICollectionViews before, until now. – Marlhex Dec 22 '21 at 15:10
7

If you'd like to perform some code after your collectionView has completed it's reloadData() method, then try this (Swift):

    self.collectionView.reloadData()
    self.collectionView.layoutIfNeeded()
    dispatch_async(dispatch_get_main_queue()) { () -> Void in
        // Put the code you want to execute when reloadData is complete in here
    }

The reason this works is because the code within the dispatch block gets put to the back of line (also known as a queue). This means that it is waiting in line for all the main thread operations to finish, including reloadData()'s methods, before it becomes it's turn on the main thread.

Alan Scarpa
  • 3,352
  • 4
  • 26
  • 48
  • 2
    well, this is not completely true, the dispatch block may be inserted between some main thread operations, see http://stackoverflow.com/a/10450867/4003774 . But your code will work (even without dispatch_async) thanks to layoutIfNeeded (see @lukewar's answer) – Matej Vargovčík Mar 14 '17 at 16:01
5

Collection view is not supported to be reloaded animatedly with help of reloadData. All animations must be performed with methods, such as

[collectionView deleteItemsAtIndexPaths:indexesToDelete];
[collectionView insertSections:sectionsToInsert];
[collectionView reloadItemsAtIndexPaths:fooPaths];

inside of performBatchUpdates: block. That reloadData method can only be used for rough refresh, when all items are removed and laid out again without animation.

kelin
  • 11,323
  • 6
  • 67
  • 104
  • I had a race between pull to refresh and api response and this helped. I simply delete and insert the sections I want to reload instead of reloadData: `self.collectionView?.performBatchUpdates({ self.collectionView?.deleteSections(IndexSet(arrayLiteral: 0)) self.collectionView?.insertSections(IndexSet(arrayLiteral: 0)) }, completion: nil)` – Aleksander Niedziolko Jun 21 '17 at 15:23