- (void)reloadMyCollectionView{
[myCollectionView reload];
[self performSelector:@selector(myStuff) withObject:nil afterDelay:0.0];
}
- (void)myStuff{
// Do your stuff here. This will method will get called once your collection view get loaded.
}
When you for example call the UICollectionView's reloadData method or it's layout's invalidateLayout method, you do the following:
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
dispatch_async(dispatch_get_main_queue(), ^{
//your stuff happens here
//after the reloadData/invalidateLayout finishes executing
});
Why this works:
The main thread (which is where we should do all UI updates) houses the main queue, which is serial in nature, i.e. it works in the FIFO fashion. So in the above example, the first block gets called, which has our reloadData method being invoked, followed by anything else in the second block.
Now the main thread is blocking as well. So if you're reloadData takes 3s to execute, the processing of the second block will be deferred by those 3s.
// In viewDidLoad
[self.collectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// You will get here when the reloadData finished
}
- (void)dealloc
{
[self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];
}
collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
// your stuff here executing after collectionView has been layouted
}
//Subclass UICollectionView
class MyCollectionView: UICollectionView {
//Store a completion block as a property
var completion: (() -> Void)?
//Make a custom funciton to reload data with a completion handle
func reloadData(completion: @escaping() -> Void) {
//Set the completion handle to the stored property
self.completion = completion
//Call super
super.reloadData()
}
//Override layoutSubviews
override func layoutSubviews() {
//Call super
super.layoutSubviews()
//Call the completion
self.completion?()
//Set the completion to nil so it is reset and doesn't keep gettign called
self.completion = nil
}
}
Then call like this inside your VC
let collection = MyCollectionView()
self.collection.reloadData(completion: {
})
The action will still be performed multiple times, as the number of visible cells at the initial state. but on all of those calls, you will have the same number of visible cells (all of them). And the boolean flag will prevent it from running again after the user started interacting with the collection view.
As dezinezync answered, what you need is to dispatch to the main queue a block of code after reloadData from a UITableView or UICollectionView, and then this block will be executed after cells dequeuing
In order to make this more straight when using, I would use an extension like this:
Try forcing a synchronous layout pass via layoutIfNeeded() right after the reloadData() call. Seems to work for both UICollectionView and UITableView on iOS 12.
collectionView.reloadData()
collectionView.layoutIfNeeded()
// cellForItem/sizeForItem calls should be complete
completion?()
Simply reload collectionView inside batch updates and then check in the completion block whether it is finished or not with the help of boolean "finish".
self.collectionView.performBatchUpdates({
self.collectionView.reloadData()
}) { (finish) in
if finish{
// Do your stuff here!
}
}
The best solution I have found so far is to use CATransaction in order to handle completion.
Swift 5:
CATransaction.begin()
CATransaction.setCompletionBlock {
// UICollectionView is ready
}
collectionView.reloadData()
CATransaction.commit()
Updated:
The above solution seems to work in some cases and in some cases it doesn't. I ended up using the accepted answer and it's definitely the most stable and proved way. Here is Swift 5 version:
private var contentSizeObservation: NSKeyValueObservation?
Most of the solutions here are not reliable or have non-deterministic behavior (which may cause random bugs), because of the confusing asynchronous nature of UICollectionView.
A reliable solution is to subclass UICollectionView to run a completion block at the end of layoutSubviews().