I am having a dataset displayed in a UICollectionView
. The dataset is split into sections and each section has a header. Further, each cell has a detail view underneath it that is expanded when the cell is clicked.
For reference:
For simplicity, I have implemented the details cells as standard cells that are hidden (height: 0) by default and when the non-detail cell is clicked, the height is set to non-zero value. The cells are updates using invalidateItems(at indexPaths: [IndexPath])
instead of reloading cells in performBatchUpdates(_ updates: (() -> Void)?, completion: ((Bool) -> Void)? = nil)
as the animations seems glitchy otherwise.
Now to the problem, the invalidateItems
function obviously updates only cells, not supplementary views like the section header and therefore calling only this function will result in overflowing the section header:
After some time Googling, I found out that in order to update also the supplementary views, one has to call invalidateSupplementaryElements(ofKind elementKind: String, at indexPaths: [IndexPath])
. This might recalculate the section header's bounds correctly, however results in the content not appearing:
This is most likely caused due to the fact that the func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
does not seem to be called.
I would be extremely grateful if somebody could tell me how to correctly invalidate supplementary views to the issues above do not happen.
Code:
override func numberOfSections(in collectionView: UICollectionView) -> Int { return dataManager.getSectionCount() } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { let count = dataManager.getSectionItemCount(section: section) reminder = count % itemsPerWidth return count * 2 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if isDetailCell(indexPath: indexPath) { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Reusable.CELL_SERVICE, for: indexPath) as! ServiceCollectionViewCell cell.lblName.text = "Americano detail" cell.layer.borderWidth = 0.5 cell.layer.borderColor = UIColor(hexString: "#999999").cgColor return cell } else { let item = indexPath.item > itemsPerWidth ? indexPath.item - (((indexPath.item / itemsPerWidth) / 2) * itemsPerWidth) : indexPath.item let product = dataManager.getItem(index: item, section: indexPath.section) let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Reusable.CELL_SERVICE, for: indexPath) as! ServiceCollectionViewCell cell.lblName.text = product.name cell.layer.borderWidth = 0.5 cell.layer.borderColor = UIColor(hexString: "#999999").cgColor return cell } } override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { switch kind { case UICollectionElementKindSectionHeader: if indexPath.section == 0 { let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: Reusable.CELL_SERVICE_HEADER_ROOT, for: indexPath) as! ServiceCollectionViewHeaderRoot header.lblCategoryName.text = "Section Header" header.imgCategoryBackground.af_imageDownloader = imageDownloader header.imgCategoryBackground.af_setImage(withURLRequest: ImageHelper.getURL(file: category.backgroundFile!)) return header } else { let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: Reusable.CELL_SERVICE_HEADER, for: indexPath) as! ServiceCollectionViewHeader header.lblCategoryName.text = "Section Header" return header } default: assert(false, "Unexpected element kind") } } // MARK: UICollectionViewDelegate func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let width = collectionView.frame.size.width / CGFloat(itemsPerWidth) if isDetailCell(indexPath: indexPath) { if expandedCell == indexPath { return CGSize(width: collectionView.frame.size.width, height: width) } else { return CGSize(width: collectionView.frame.size.width, height: 0) } } else { return CGSize(width: width, height: width) } } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { if section == 0 { return CGSize(width: collectionView.frame.width, height: collectionView.frame.height / 3) } else { return CGSize(width: collectionView.frame.width, height: heightHeader) } } override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if isDetailCell(indexPath: indexPath) { return } var offset = itemsPerWidth if isLastRow(indexPath: indexPath) { offset = reminder } let detailPath = IndexPath(item: indexPath.item + offset, section: indexPath.section) let context = UICollectionViewFlowLayoutInvalidationContext() let maxItem = collectionView.numberOfItems(inSection: 0) - 1 var minItem = detailPath.item if let expandedCell = expandedCell { minItem = min(minItem, expandedCell.item) } // TODO: optimize this var cellIndexPaths = (0 ... maxItem).map { IndexPath(item: $0, section: 0) } var supplementaryIndexPaths = (0..<collectionView.numberOfSections).map { IndexPath(item: 0, section: $0)} for i in indexPath.section..<collectionView.numberOfSections { cellIndexPaths.append(contentsOf: (0 ... collectionView.numberOfItems(inSection: i) - 1).map { IndexPath(item: $0, section: i) }) //supplementaryIndexPaths.append(IndexPath(item: 0, section: i)) } context.invalidateSupplementaryElements(ofKind: UICollectionElementKindSectionHeader, at: supplementaryIndexPaths) context.invalidateItems(at: cellIndexPaths) if detailPath == expandedCell { expandedCell = nil } else { expandedCell = detailPath } UIView.animate(withDuration: 0.25) { collectionView.collectionViewLayout.invalidateLayout(with: context) collectionView.layoutIfNeeded() } }
1 Answers
Answers 1
If you call your collectionView.layoutIfNeeded() within your animate function the animation doesn't work. I suggest you reload the collectionView as you should have invalidated the supplement views it should give you the required results. If however that will not work then you can try adding a couple of print statements to see if the invalidatedSupplementViews are registered correctly.
// TODO: optimize this var cellIndexPaths = (0 ... maxItem).map { IndexPath(item: $0, section: 0) } var supplementaryIndexPaths = (0..<collectionView.numberOfSections).map { IndexPath(item: 0, section: $0)} for i in indexPath.section..<collectionView.numberOfSections { cellIndexPaths.append(contentsOf: (0 ... collectionView.numberOfItems(inSection: i) - 1).map { IndexPath(item: $0, section: i) }) //supplementaryIndexPaths.append(IndexPath(item: 0, section: i)) } context.invalidateSupplementaryElements(ofKind: UICollectionElementKindSectionHeader, at: supplementaryIndexPaths) context.invalidateItems(at: cellIndexPaths) print("____________INFO________________") print("THE SUPPLEMENTARY INDEX PATHS ARE: \(supplementaryIndexPaths))" print("------CONTEXT-----") print("\(context)") print("__________END OF DEBUG INFO_________") if detailPath == expandedCell { expandedCell = nil } else { expandedCell = detailPath } UIView.animate(withDuration: 0.25) { collectionView.collectionViewLayout.invalidateLayout(with: context) } collectionView.layoutIfNeeded() //if this doesn't work then try uncommenting the next line //collectionView.reloadData() }
If all this will not help then paste what you get in those debug print statements. Or you could share the code on github I could try to contribute.
EDIT 1:
// MARK: UICollectionViewDelegate //add @objc to the func below @objc func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let width = collectionView.frame.size.width / CGFloat(itemsPerWidth) if isDetailCell(indexPath: indexPath) { if expandedCell == indexPath { return CGSize(width: collectionView.frame.size.width, height: width) } else { return CGSize(width: collectionView.frame.size.width, height: 0) } } else { return CGSize(width: width, height: width) } }
0 comments:
Post a Comment