My cell has an image which be downloaded from network, therefore I need to set the height of cell as dynamic.
When an image download is finished, I am going to call self.collectionView.reloadItems(at: [indexPath])
to trigger the delegate method for setting a new height.
But it seems that the reloadItems
method will recreate a cell, not just re-layout an original reuse cell.
How can I solve this problem? Is it a bug on UICollectionView from apple or something wrong I did?
Whole code:
// code from ViewController
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AnnounmentWallCollectionViewCell.cellIdentifier, for: indexPath) as! AnnounmentWallCollectionViewCell
let announcement = announcements[indexPath.row]
cell.collectionView = collectionView
cell.setBanner(from: announcement.banner, indexPath: indexPath, completion: { [unowned self] (height) in
self.bannersHeight[indexPath.row] = height
})
cell.setHTMLContent(announcement.content)
contentsHeight[indexPath.row] = cell.htmlContentSize.height
printD("indexPath: \(indexPath)")
return cell
}
// code from cell
func setBanner(from url: URL?, indexPath: IndexPath, completion: @escaping (_ height: CGFloat)->()) {
// URL(string: "https://i.imgur.com/qzY7BJ9.jpg")
if let url = url {
if let banner = SDImageCache.shared().imageFromDiskCache(forKey: url.absoluteString) {
self.bannerView.isHidden = false
self.bannerView.image = banner.scaleWidth(to: self.bounds.width - 32) // leading + trailling
self.bannerHeight.constant = self.bannerView.image?.size.height ?? 1
completion(self.bannerHeight.constant)
printD("NO Download: \(indexPath)")
let animationsEnabled = UIView.areAnimationsEnabled
UIView.setAnimationsEnabled(false)
self.collectionView.reloadItems(at: [indexPath])
UIView.setAnimationsEnabled(animationsEnabled)
} else {
DispatchQueue.global().async {
SDWebImageDownloader.shared().downloadImage(with: url, options: .useNSURLCache, progress: nil) { (banner, data, error, finished) in
DispatchQueue.main.async {
if let banner = banner {
SDImageCache.shared().store(banner, forKey: url.absoluteString, toDisk: true)
self.bannerView.isHidden = false
self.bannerHeight.constant = banner.scaleWidth(to: self.bounds.width - 32)?.size.height ?? 1
completion(self.bannerHeight.constant)
self.collectionView.reloadData()
printD("Download: \(indexPath): \(self.bannerHeight.constant)")
} else {
self.bannerView.isHidden = true
self.bannerHeight.constant = 1
completion(self.bannerHeight.constant)
}
}
}
}
}
} else {
bannerView.isHidden = true
bannerHeight.constant = 1
completion(bannerHeight.constant)
}
}
// code from delegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = self.view.bounds.width
let height = bannersHeight[indexPath.row] + contentsHeight[indexPath.row]
+ 1 // sticker
+ 11 // banner top
printD("indexPath: \(indexPath): \(height)")
return CGSize(width: width, height: height)
}
That's not a bug. That's how you reload a cell for a given index path. If you only want to update the layout you can also try
[self.collectionView.collectionViewLayout invalidateLayout]
instead of
self.collectionView.reloadItems(at: [indexPath])
and then return the proper size in the delegate method.
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return //whatever size that you want to return.
}
I would also highly suggest you to cache your image sizes so that you can use that for the next time, instead of downloading/calculating over an over...