How do I know that the UICollectionView has been loaded completely?
UicollectionviewDelegatesReloadUicollectionview Problem Overview
I have to do some operation whenever UICollectionView has been loaded completely, i.e. at that time all the UICollectionView's datasource / layout methods should be called. How do I know that?? Is there any delegate method to know UICollectionView loaded status?
Uicollectionview Solutions
Solution 1 - Uicollectionview
This worked for me:
[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^{}
completion:^(BOOL finished) {
/// collection-view finished reload
}];
Swift 4 syntax:
collectionView.reloadData()
collectionView.performBatchUpdates(nil, completion: {
(result) in
// ready
})
Solution 2 - Uicollectionview
// 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];
}
Solution 3 - Uicollectionview
It's actually rather very simple.
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.
Solution 4 - Uicollectionview
Just to add to a great @dezinezync answer:
Swift 3+
collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
// your stuff here executing after collectionView has been layouted
}
Solution 5 - Uicollectionview
A different approaching using RxSwift/RxCocoa:
collectionView.rx.observe(CGSize.self, "contentSize")
.subscribe(onNext: { size in
print(size as Any)
})
.disposed(by: disposeBag)
Solution 6 - Uicollectionview
Do it like this:
UIView.animateWithDuration(0.0, animations: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.collectionView.reloadData()
}, completion: { [weak self] (finished) in
guard let strongSelf = self else { return }
// Do whatever is needed, reload is finished here
// e.g. scrollToItemAtIndexPath
let newIndexPath = NSIndexPath(forItem: 1, inSection: 0)
strongSelf.collectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
})
Solution 7 - Uicollectionview
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:
extension UICollectionView {
func reloadData(_ completion: @escaping () -> Void) {
reloadData()
DispatchQueue.main.async { completion() }
}
}
It can be also implemented to a UITableView
as well
Solution 8 - Uicollectionview
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?()
Solution 9 - Uicollectionview
I just did the following to perform anything after collection view is reloaded. You can use this code even in API response.
self.collectionView.reloadData()
DispatchQueue.main.async {
// Do Task after collection view is reloaded
}
Solution 10 - Uicollectionview
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?
contentSizeObservation = collectionView.observe(\.contentSize) { [weak self] _, _ in
self?.contentSizeObservation = nil
completion()
}
collectionView.reloadData()
Solution 11 - Uicollectionview
SWIFT 5
override func viewDidLoad() {
super.viewDidLoad()
// "collectionViewDidLoad" for transitioning from product's cartView to it's cell in that view
self.collectionView?.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let observedObject = object as? UICollectionView, observedObject == self.collectionView {
print("collectionViewDidLoad")
self.collectionView?.removeObserver(self, forKeyPath: "contentSize")
}
}
Solution 12 - Uicollectionview
This works for me:
__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
[wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
[wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];
Solution 13 - Uicollectionview
I needed some action to be done on all of the visible cells when the collection view get loaded before it is visible to the user, I used:
public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if shouldPerformBatch {
self.collectionView.performBatchUpdates(nil) { completed in
self.modifyVisibleCells()
}
}
}
Pay attention that this will be called when scrolling through the collection view, so to prevent this overhead, I added:
private var souldPerformAction: Bool = true
and in the action itself:
private func modifyVisibleCells() {
if self.shouldPerformAction {
// perform action
...
...
}
self.shouldPerformAction = false
}
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.
Solution 14 - Uicollectionview
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!
}
}
Solution 15 - Uicollectionview
Def do this:
//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: {
})
Make sure you are using the subclass!!
Solution 16 - Uicollectionview
This work for me:
- (void)viewDidLoad {
[super viewDidLoad];
int ScrollToIndex = 4;
[self.UICollectionView performBatchUpdates:^{}
completion:^(BOOL finished) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:ScrollToIndex inSection:0];
[self.UICollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}];
}
Solution 17 - Uicollectionview
Below is the only approach that worked for me.
extension UICollectionView {
func reloadData(_ completion: (() -> Void)? = nil) {
reloadData()
guard let completion = completion else { return }
layoutIfNeeded()
completion()
}
}
Solution 18 - Uicollectionview
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()
.
Code in Objectice-C: https://stackoverflow.com/a/39648633
Code in Swift: https://stackoverflow.com/a/39798079
Solution 19 - Uicollectionview
This is how I solved problem with Swift 3.0:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.collectionView.visibleCells.isEmpty {
// stuff
}
}
Solution 20 - Uicollectionview
Try this:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return _Items.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell;
//Some cell stuff here...
if(indexPath.row == _Items.count-1){
//THIS IS THE LAST CELL, SO TABLE IS LOADED! DO STUFF!
}
return cell;
}
Solution 21 - Uicollectionview
You can do like this...
- (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.
}