Happy New Year! ??
I'm trying to make a custom layout with animated inserts and deletions of sections and elements. At the same time, it is important that all the elements are self-sized.
Everything works as it should, except for one thing: The request to get its size, the header receives the very last, although I need it to receive it the very first. Because of this, my cells are in the wrong position at the start of the animation.
I'll try to explain in more detail ...
- It all starts with a method call
prepare(forAnimatedBoundsChange:)
, where I update my internal attributes model.
initialLayoutAttributesForAppearingItem(at:)
method is called, where for each cell that is contained in the section that I insert, and before returning attributes, I copy the attributes in order to be able to change them, since I get their final size later.
override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if itemIndexPathsToInsert.contains(itemIndexPath) || sectionIndicesToInsert.contains(itemIndexPath.section) {
let item = sections.item(at: itemIndexPath).copy() as! InsetGroupedLayoutAttributes
item.alpha = 0.0
itemsForPendingAnimations[itemIndexPath] = item
return item
} else {
return super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)
}
}
3 Then invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)
method is called where I get the final size
override func invalidationContext(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutInvalidationContext {
let invalidationContext = super.invalidationContext(forPreferredLayoutAttributes: preferredAttributes, withOriginalAttributes: originalAttributes)
let indexPath = preferredAttributes.indexPath
let height = preferredAttributes.frame.height.rounded(.up)
switch preferredAttributes.representedElementCategory {
case .cell:
print("cell", #function)
sections.updateItem(at: indexPath, withNewHeight: height)
itemsForPendingAnimations[indexPath]?.frame = sections.item(at: indexPath).frame
// itemsForPendingAnimations[indexPath]?.transform = .init(scaleX: 0.8, y: 0.8)
invalidationContext.invalidateItems(at: [indexPath])
case .supplementaryView:
print("supplementary", #function)
sections.updateHeader(forSectionIndex: indexPath.section, withNewHeight: height)
supplementaryElementsForPendingAnimations[indexPath]?.frame = sections.supplementaryElement(at: indexPath).frame
itemsForPendingAnimations.forEach { $0.value.frame = sections.item(at: $0.key).frame }
invalidationContext.invalidateSupplementaryElements(ofKind: InsetGroupedHeaderCollectionReusableView.kind, at: [indexPath])
default:
assertionFailure()
}
return invalidationContext
}
- Finally,
layoutAttributesForItem(at:)
method is called, after which, I can no longer change the attributes that I copied, the animation will start from the state in which the attributes remained after calling this method.
Further, calls from 2 to 4 will be repeated for all cells of the inserted sections AND ONLY AFTER the corresponding methods are called for supplementary elements. initialLayoutAttributesForAppearingSupplementaryElement(ofKind:at:)
, invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)
and layoutAttributesForSupplementaryView(ofKind:at:)
. Here my headers gets its final size, and all the cells after this title should also be resized, but I can no longer change the attributes for the initial state of the animations.
Logging calls to insert one of the sections:
prepare(forCollectionViewUpdates:)
initialLayoutAttributesForAppearingItem(at:) [1, 0]
cell invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)
layoutAttributesForItem(at:) [1, 0]
initialLayoutAttributesForAppearingItem(at:) [1, 1]
cell invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)
layoutAttributesForItem(at:) [1, 1]
initialLayoutAttributesForAppearingItem(at:) [1, 2]
cell invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)
layoutAttributesForItem(at:) [1, 2]
initialLayoutAttributesForAppearingItem(at:) [1, 3]
cell invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)
layoutAttributesForItem(at:) [1, 3]
initialLayoutAttributesForAppearingItem(at:) [1, 4]
cell invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)
layoutAttributesForItem(at:) [1, 4]
initialLayoutAttributesForAppearingSupplementaryElement(ofKind:at:) [1, 0] <--- Should be called first
supplementary invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:) <--- Should be called first
layoutAttributesForSupplementaryView(ofKind:at:) [1, 0] <--- Should be called first
finalizeCollectionViewUpdates()
As a result, the insertion occurs with a slight movement of the cells, since I can no longer change their position after resizing the header:
https://youtu.be/Lbj4ETO5HC0
question from:
https://stackoverflow.com/questions/65540708/uicollectionviewlayout-section-insertion-with-self-sizing