diff --git a/Source/IGListKit/IGListAdapter.m b/Source/IGListKit/IGListAdapter.m index 1195708f..92c7a57a 100644 --- a/Source/IGListKit/IGListAdapter.m +++ b/Source/IGListKit/IGListAdapter.m @@ -901,10 +901,16 @@ } - (NSArray *)fullyVisibleCellsForSectionController:(IGListSectionController *)sectionController { + const NSInteger section = [self sectionForSectionController:sectionController]; + if (section == NSNotFound) { + // The section controller is not in the map, which can happen if the associated object was deleted or after a full reload. + return @[]; + } + NSMutableArray *cells = [NSMutableArray new]; UICollectionView *collectionView = self.collectionView; NSArray *visibleCells = [collectionView visibleCells]; - const NSInteger section = [self sectionForSectionController:sectionController]; + for (UICollectionViewCell *cell in visibleCells) { if ([collectionView indexPathForCell:cell].section == section) { const CGRect cellRect = [cell convertRect:cell.bounds toView:collectionView]; @@ -917,10 +923,16 @@ } - (NSArray *)visibleCellsForSectionController:(IGListSectionController *)sectionController { + const NSInteger section = [self sectionForSectionController:sectionController]; + if (section == NSNotFound) { + // The section controller is not in the map, which can happen if the associated object was deleted or after a full reload. + return @[]; + } + NSMutableArray *cells = [NSMutableArray new]; UICollectionView *collectionView = self.collectionView; NSArray *visibleCells = [collectionView visibleCells]; - const NSInteger section = [self sectionForSectionController:sectionController]; + for (UICollectionViewCell *cell in visibleCells) { if ([collectionView indexPathForCell:cell].section == section) { [cells addObject:cell]; @@ -930,10 +942,16 @@ } - (NSArray *)visibleIndexPathsForSectionController:(IGListSectionController *) sectionController { + const NSInteger section = [self sectionForSectionController:sectionController]; + if (section == NSNotFound) { + // The section controller is not in the map, which can happen if the associated object was deleted or after a full reload. + return @[]; + } + NSMutableArray *paths = [NSMutableArray new]; UICollectionView *collectionView = self.collectionView; NSArray *visiblePaths = [collectionView indexPathsForVisibleItems]; - const NSInteger section = [self sectionForSectionController:sectionController]; + for (NSIndexPath *path in visiblePaths) { if (path.section == section) { [paths addObject:path]; @@ -1105,8 +1123,16 @@ } - (void)invalidateLayoutForSectionController:(IGListSectionController *)sectionController - completion:(void (^)(BOOL finished))completion{ + completion:(void (^)(BOOL finished))completion { const NSInteger section = [self sectionForSectionController:sectionController]; + if (section == NSNotFound) { + // The section controller is not in the map, which can happen if the associated object was deleted or after a full reload. + if (completion) { + completion(NO); + } + return; + } + const NSInteger items = [_collectionView numberOfItemsInSection:section]; NSMutableArray *indexPaths = [NSMutableArray new]; diff --git a/Tests/IGListAdapterTests.m b/Tests/IGListAdapterTests.m index 86a985ce..c9b008a8 100644 --- a/Tests/IGListAdapterTests.m +++ b/Tests/IGListAdapterTests.m @@ -1843,4 +1843,57 @@ [mockDelegate verify]; } +#pragma mark - Deleted Section Controllers + +- (void)test_whenSectionControllerRemoved_thatCellForIndexPathIsNil { + self.dataSource.objects = @[@1]; + [self.adapter performUpdatesAnimated:NO completion:nil]; + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:@1]; + self.dataSource.objects = @[@2]; + [self.adapter performUpdatesAnimated:NO completion:nil]; + XCTAssertNil([sectionController.collectionContext cellForItemAtIndex:0 sectionController:sectionController]); +} + +- (void)test_whenSectionControllerRemoved_thatFullyVisibleCellsIsEmpty { + self.dataSource.objects = @[@1]; + [self.adapter performUpdatesAnimated:NO completion:nil]; + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:@1]; + self.dataSource.objects = @[@2]; + [self.adapter performUpdatesAnimated:NO completion:nil]; + + NSArray *cells = [sectionController.collectionContext fullyVisibleCellsForSectionController:sectionController]; + XCTAssertEqual(cells.count, 0); +} + +- (void)test_whenSectionControllerRemoved_thatVisibleCellsIsEmpty { + self.dataSource.objects = @[@1]; + [self.adapter performUpdatesAnimated:NO completion:nil]; + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:@1]; + self.dataSource.objects = @[@2]; + [self.adapter performUpdatesAnimated:NO completion:nil]; + + NSArray *cells = [sectionController.collectionContext visibleCellsForSectionController:sectionController]; + XCTAssertEqual(cells.count, 0); +} + +- (void)test_whenSectionControllerRemoved_thatVisibleIndexPathIsEmpty { + self.dataSource.objects = @[@1]; + [self.adapter performUpdatesAnimated:NO completion:nil]; + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:@1]; + self.dataSource.objects = @[@2]; + [self.adapter performUpdatesAnimated:NO completion:nil]; + + NSArray *cells = [sectionController.collectionContext visibleIndexPathsForSectionController:sectionController]; + XCTAssertEqual(cells.count, 0); +} + +- (void)test_whenSectionControllerRemoved_thatDoesNotCrashOnInvalidatingLayout { + self.dataSource.objects = @[@1]; + [self.adapter performUpdatesAnimated:NO completion:nil]; + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:@1]; + self.dataSource.objects = @[@2]; + [self.adapter performUpdatesAnimated:NO completion:nil]; + [sectionController.collectionContext invalidateLayoutForSectionController:sectionController completion:nil]; +} + @end