diff --git a/StableCollectionViewLayout/Sources/CollectionViewDataProvider.swift b/StableCollectionViewLayout/Sources/CollectionViewDataProvider.swift index fe24f4f..cbef81c 100644 --- a/StableCollectionViewLayout/Sources/CollectionViewDataProvider.swift +++ b/StableCollectionViewLayout/Sources/CollectionViewDataProvider.swift @@ -20,7 +20,17 @@ public protocol CollectionViewDataProvider: AnyObject { public extension CollectionViewDataProvider { func isValid(indexPath: IndexPath) -> Bool { - numberOfSections > indexPath.section - && numberOfItems(inSection: indexPath.section) > indexPath.row + let section = indexPath.section + let item = indexPath.item + + guard section >= 0, item >= 0 else { + return false + } + + guard section < numberOfSections else { + return false + } + + return item < numberOfItems(inSection: section) } } diff --git a/StableCollectionViewLayout/Sources/OffsetController/OffsetControllerImpl.swift b/StableCollectionViewLayout/Sources/OffsetController/OffsetControllerImpl.swift index b5e0e71..ebb08d0 100644 --- a/StableCollectionViewLayout/Sources/OffsetController/OffsetControllerImpl.swift +++ b/StableCollectionViewLayout/Sources/OffsetController/OffsetControllerImpl.swift @@ -96,7 +96,7 @@ public class OffsetControllerImpl: OffsetController { let previousVisibleFrame = previousVisibleAttributes[visibleState.targetIndexPath] ?? .zero - for item in updates { + for item in updates where isSane(updateItem: item) { visibleStateController.update(with: item) } @@ -115,6 +115,23 @@ public class OffsetControllerImpl: OffsetController { return calculatedOffsetDiff } + private func isSane(updateItem: CollectionViewUpdateItem) -> Bool { + isSane(indexPath: updateItem.indexPathBeforeUpdate, isSection: updateItem.isSection) + && isSane(indexPath: updateItem.indexPathAfterUpdate, isSection: updateItem.isSection) + } + + private func isSane(indexPath: IndexPath?, isSection: Bool) -> Bool { + guard let indexPath else { + return true + } + + if isSection { + return indexPath.section >= 0 && indexPath.item == Int.max + } + + return indexPath.section >= 0 && indexPath.item >= 0 + } + private func refreshVisibleState() { guard let collectionDataSource = collectionDataSource else { return