Post
Replies
Boosts
Views
Activity
It seems this has been an issue for over a week now. I have already tried everything, from a different browser to a different network, still nothing.
Seems to be working now.
It seems I have solved the issue by using item IDs instead of IndexPaths to cache layout attributes.
Here is the final UICollectionViewLayout code (rough draft, not cleaned up):
final class CustomLayout: UICollectionViewLayout {
// Configurable properties
public var numberOfColumns: Int = 6
public var cellHeight: Double = 200
public var cellSpacing: Double = 20
public var rowSpacing: Double = 20
public var sectionInsets: NSDirectionalEdgeInsets = .zero
public var layoutAttributes: [String: UICollectionViewLayoutAttributes] = [:]
private var collectionViewDataSource: UICollectionViewDiffableDataSource<String, String>? {
return collectionView?.dataSource as? UICollectionViewDiffableDataSource<String, String>
}
override func prepare() {
super.prepare()
guard
let collectionView,
let collectionViewDataSource
else {
return
}
var updatedLayoutAttributes: [String: UICollectionViewLayoutAttributes] = [:]
let columnWidth: Double = (collectionView.bounds.width - cellSpacing * Double(numberOfColumns - 1) - sectionInsets.leading - sectionInsets.trailing) / Double(numberOfColumns)
let numberOfSections: Int = collectionView.numberOfSections
for section in 0..<numberOfSections {
var currentColumn: Int = 0
var currentRow: Int = 0
let numberOfItems: Int = collectionView.numberOfItems(inSection: section)
for item in 0..<numberOfItems {
let itemIndexPath = IndexPath(item: item, section: section)
let itemAttributes = UICollectionViewLayoutAttributes(forCellWith: itemIndexPath)
guard let itemID = collectionViewDataSource.itemIdentifier(for: itemIndexPath) else {
return
}
let itemHeight = layoutAttributes[itemID]?.bounds.height ?? 140
let itemWidth = columnWidth
if currentColumn + 1 > numberOfColumns {
currentColumn = 0
currentRow += 1
}
let originX = sectionInsets.leading + columnWidth * Double(currentColumn) + cellSpacing * Double(currentColumn)
let originY = sectionInsets.top + cellHeight * Double(currentRow) + rowSpacing * Double(currentRow)
if let existingAttributes = layoutAttributes[itemID] {
print("Using existing attributes for: \(existingAttributes.indexPath)")
itemAttributes.frame = CGRect(
x: originX,
y: originY,
width: existingAttributes.frame.width,
height: existingAttributes.frame.height
)
}
else {
itemAttributes.frame = CGRect(
x: originX,
y: originY,
width: itemWidth,
height: itemHeight
)
}
itemAttributes.zIndex = itemIndexPath.item
updatedLayoutAttributes[itemID] = itemAttributes
currentColumn += 1
}
}
layoutAttributes = updatedLayoutAttributes
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var allAttributes: [UICollectionViewLayoutAttributes] = []
for (_, attributes) in layoutAttributes {
if (rect.intersects(attributes.frame)) {
allAttributes.append(attributes)
}
}
return allAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let collectionViewDataSource else {
return nil
}
guard let itemID = collectionViewDataSource.itemIdentifier(for: indexPath) else {
return nil
}
return layoutAttributes[itemID]
}
override var collectionViewContentSize: CGSize {
guard let collectionView else {
return .zero
}
let contentHeight: CGFloat = layoutAttributes.map({ $0.value.frame.maxY }).max() ?? 0
return CGSize(width: collectionView.bounds.width, height: contentHeight)
}
override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool {
return originalAttributes.frame.height.rounded() != preferredAttributes.frame.height.rounded()
}
override func invalidationContext(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forPreferredLayoutAttributes: preferredAttributes, withOriginalAttributes: originalAttributes)
guard let collectionViewDataSource else {
return context
}
guard let itemID = collectionViewDataSource.itemIdentifier(for: preferredAttributes.indexPath) else {
return context
}
layoutAttributes[itemID]?.frame.size = preferredAttributes.frame.size
context.invalidateItems(at: [preferredAttributes.indexPath])
return context
}
}