I set up a UICollectionView
that has a following settings:
It's pretty much like a UITableView
, which works fine in most cases, except one specific situation when the animation doesn't apply.
Among stacked cells in collectionView
, say one of the upper cells expands its height. Then the lower cell must be moving downwards to keep the distance. If this moving cell's target frame is out of collectionView's
bounds, then no animation applies and the cell disappears.
Opposite case works the same way; if the lower cell's source frame is out of screen bounds (currently outside of the bounds) and the upper cell should shrink, no animation applies and it just appear on target frame.
This seems appropriate in memory management logic controlled by UICollectionView
, but at the same time nothing natural to show users that some of contents just appear or disappear out of blue. I had tested this with UITableView and the same thing happens.
Is there a workaround for this issue?
You should add some code or at least a gif of your UI problem.
I tried to replicate your problem using a basic UICollectionViewLayout
subclass :
protocol CollectionViewLayoutDelegate: AnyObject {
func heightForItem(at indexPath: IndexPath) -> CGFloat
}
class CollectionViewLayout: UICollectionViewLayout {
weak var delegate: CollectionViewLayoutDelegate?
private var itemAttributes: [UICollectionViewLayoutAttributes] = []
override func prepare() {
super.prepare()
itemAttributes = generateItemAttributes()
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
return collectionView?.contentOffset ?? .zero
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return itemAttributes.first { $0.indexPath == indexPath }
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return itemAttributes.filter { $0.frame.intersects(rect) }
}
private func generateItemAttributes() -> [UICollectionViewLayoutAttributes] {
var offset: CGFloat = 0
return (0..<numberOfItems()).map { index in
let indexPath = IndexPath(item: index, section: 0)
let frame = CGRect(
x: 0,
y: offset,
width: collectionView?.bounds.width ?? 0,
height: delegate?.heightForItem(at: indexPath) ?? 0
)
offset = frame.maxY
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = frame
return attributes
}
}
}
In a simple UIViewController
, I reloaded the first cell each time a cell is selected:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
updatedIndexPath = IndexPath(item: 0, section: 0)
collectionView.reloadItems(at: [updatedIndexPath])
}
In that case, I faced an animation like this:
I think you could try to tweak the attributes returned by super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath)
computing its correct frame and play with the z-index.
But you could also simply try to invalidate all the layout like so:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
layout = CollectionViewLayout()
layout.delegate = self
collectionView.setCollectionViewLayout(layout, animated: true)
}
and override:
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
return collectionView?.contentOffset ?? .zero
}
to avoid a wrong target content offset computation when the layout is invalidated.
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