I'm probably doing something incredibly stupid, but I can't figure out for the life of me what's going on...
What I'm trying to do is quite simple. I have a UICollectionView
dependent on data I'm loading from the server. After I get and set the data from the server, I call reloadData()
on the collection view. When I do so, numberOfItemsInSection
is called with the correct count, but cellForItemAt
never gets called unless I call reloadData()
for a second time (doesn't even need a delay).
Here's the general idea (although I realize it's likely related to some race conditional elsewhere, but maybe this will be enough to start the conversation):
class MyViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
@IBOutlet weak var collectionView: UICollectionView!
var data = [DataModel]()
override func viewDidLoad() {
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "MyCell", bundle: nil), forCellWithReuseIdentifier: "MyCell")
getData()
}
func getData() {
someAsyncMethodToGetData() { (data) in
// Using CloudKit so I think this is necessary, but tried it with and without
DispatchQueue.main.async {
self.data = data
self.collectionView.reloadData()
// This is the only way to get cells to render and for cellForItemAt to be called after data is loaded
self.collectionView.reloadData()
}
}
// Gets called initially AND after reloadData is called in the data completion closure. First time is 0, next time is number of DataModel objects
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
// Only gets called initially (unless I call reloadData() twice)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = feedCollectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as? MyCell else {
return UICollectionViewCell()
}
// Configure the cell with data...
return cell
}
}
So here are some things I've already tried:
reloadData()
is being called on the main thread (added a symbolic breakpoint and called Thread.isMainThread
to verify)reloadData()
into a button action to eliminate the possibility of any thread issues - still had to click the button twice to get cells to show upperformBatchUpdates
based on some other SO solutions I came acrosssetNeedsLayout
and setNeedsDisplay
(not sure why this would be necessary in this case, but I did try it out of desperation.Any suggestions? 🙏
Ok, think I found the issue, but I'd like to understand the best way to solve. It appears by calling reloadData
, the collection view will only reload cells already visible. I hacked the code above a bit to return 1 cell before getData()
is called. When getData()
then calls reloadData()
that 1 cell gets updated (but if there are 10 items in the data array, it will still only [re]display the 1 cell). I suppose this makes sense, as reloadData()
only reloads visible cells, but this has to be a common issue when the length of the array changes and there is still room on the screen to display. Also, why does calling reloadData()
twice solve the issue and display cells that were never visible? 🤔
Here is an even simpler version of the code I have above, removing the whole "get data from a server" idea and replacing with a simple delay.
The thing is UICollectionViewFlowLayout isn't invalidated correctly after number of items changed. So collectionView.contentSize
is still old. You need to do something like
self.collectionView.reloadData()
self.collectionView.collectionViewLayout.invalidateLayout()
This will tell layout to recalculate for new data source.
First make sure if the delegate and the dataSource is correctly add to collectionView.
Second make sure if the data is fill whit breakpoint or a simple print
Third add this:
DispatchQueque.main.async{
self.collection.reloadData()
self.collection.collectionViewLayout.invalidateLayout()
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With