diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc961ec..e63a522e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,9 @@ This release closes the [3.0.0 milestone](https://github.com/Instagram/IGListKit - Invalidate the layout of a section controller and control the transition with `UIView` animation APIs. [Ryan Nystrom](https://github.com/rnystrom) [(#499)](https://github.com/Instagram/IGListKit/pull/499) +- Added `-[IGListAdapter visibleIndexPathsForSectionController:]` API. [Malecks](https://github.com/Malecks) [(#465)](https://github.com/Instagram/IGListKit/pull/465) + + ### Fixes - Gracefully handle a `nil` section controller returned by an `IGListAdapterDataSource`. [Ryan Nystrom](https://github.com/rnystrom) [(tbd)](https://github.com/Instagram/IGListKit/pull/tbd) diff --git a/Source/IGListAdapter.m b/Source/IGListAdapter.m index 0410ce79..8398f3e5 100644 --- a/Source/IGListAdapter.m +++ b/Source/IGListAdapter.m @@ -865,6 +865,19 @@ return cells; } +- (NSArray *)visibleIndexPathsForSectionController:(IGListSectionController *) sectionController { + 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]; + } + } + return paths; +} + - (void)deselectItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController animated:(BOOL)animated { diff --git a/Source/IGListCollectionContext.h b/Source/IGListCollectionContext.h index 80672d72..ea357b5e 100644 --- a/Source/IGListCollectionContext.h +++ b/Source/IGListCollectionContext.h @@ -58,6 +58,14 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSArray *)visibleCellsForSectionController:(IGListSectionController *)sectionController; +/** + Returns the visible paths for the given section controller. + + @param sectionController The section controller requesting this information. + @return An array of visible index paths, or an empty array if none are found. + */ +- (NSArray *)visibleIndexPathsForSectionController:(IGListSectionController *) sectionController; + /** Deselects a cell in the collection. diff --git a/Source/IGListStackedSectionController.m b/Source/IGListStackedSectionController.m index 90e13843..1a5f6efa 100644 --- a/Source/IGListStackedSectionController.m +++ b/Source/IGListStackedSectionController.m @@ -187,6 +187,18 @@ static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerInde return cells; } +- (NSArray *)visibleIndexPathsForSectionController:(IGListSectionController *)sectionController { + NSMutableArray *paths = [NSMutableArray new]; + id collectionContext = self.collectionContext; + NSArray *visiblePaths = [collectionContext visibleIndexPathsForSectionController:self]; + for (NSIndexPath *path in visiblePaths) { + if (self.sectionControllersForItems[path.item] == sectionController) { + [paths addObject:path]; + } + } + return paths; +} + - (void)deselectItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController animated:(BOOL)animated { const NSInteger offsetIndex = [self relativeIndexForSectionController:sectionController fromLocalIndex:index]; [self.collectionContext deselectItemAtIndex:offsetIndex sectionController:self animated:animated]; diff --git a/Tests/IGListAdapterTests.m b/Tests/IGListAdapterTests.m index f0302f20..d34fc348 100644 --- a/Tests/IGListAdapterTests.m +++ b/Tests/IGListAdapterTests.m @@ -313,6 +313,21 @@ XCTAssertEqual(CGPointEqualToPoint(point, p), YES); \ XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController6].count, 0); } +- (void)test_whenCellsExtendBeyondBounds_thatVisibleIndexPathsExistForSectionControllers { + self.dataSource.objects = @[@2, @3, @4, @5, @6]; + [self.adapter reloadDataWithCompletion:nil]; + id sectionController2 = [self.adapter sectionControllerForObject:@2]; + id sectionController3 = [self.adapter sectionControllerForObject:@3]; + id sectionController4 = [self.adapter sectionControllerForObject:@4]; + id sectionController5 = [self.adapter sectionControllerForObject:@5]; + id sectionController6 = [self.adapter sectionControllerForObject:@6]; + XCTAssertEqual([self.adapter visibleIndexPathsForSectionController:sectionController2].count, 2); + XCTAssertEqual([self.adapter visibleIndexPathsForSectionController:sectionController3].count, 3); + XCTAssertEqual([self.adapter visibleIndexPathsForSectionController:sectionController4].count, 4); + XCTAssertEqual([self.adapter visibleIndexPathsForSectionController:sectionController5].count, 1); + XCTAssertEqual([self.adapter visibleIndexPathsForSectionController:sectionController6].count, 0); +} + - (void)test_whenDataSourceAddsItems_thatEmptyViewBecomesVisible { self.dataSource.objects = @[]; UIView *background = [UIView new]; diff --git a/Tests/IGListStackSectionControllerTests.m b/Tests/IGListStackSectionControllerTests.m index e532fe64..3251a065 100644 --- a/Tests/IGListStackSectionControllerTests.m +++ b/Tests/IGListStackSectionControllerTests.m @@ -416,6 +416,52 @@ static const CGRect kStackTestFrame = (CGRect){{0.0, 0.0}, {100.0, 100.0}}; XCTAssertEqual([stack visibleCellsForSectionController:section5].count, 0); } +- (void)test_whenQueryingVisibleSectionControllers_withIndexPathsOffscreen_thatOnlyVisibleReturned { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@3, @4, @0, @5, @6]] + ]]; + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + + IGListTestSection *section1 = stack.sectionControllers[0]; + IGListTestSection *section2 = stack.sectionControllers[1]; + IGListTestSection *section3 = stack.sectionControllers[2]; + IGListTestSection *section4 = stack.sectionControllers[3]; + IGListTestSection *section5 = stack.sectionControllers[4]; + + NSSet *visible1 = [NSSet setWithArray:[stack visibleIndexPathsForSectionController:section1]]; + NSSet *expected1 = [NSSet setWithArray:@[ + [NSIndexPath indexPathForItem:0 inSection:0], + [NSIndexPath indexPathForItem:1 inSection:0], + [NSIndexPath indexPathForItem:2 inSection:0], + ]]; + XCTAssertEqualObjects(visible1, expected1); + + NSSet *visible2 = [NSSet setWithArray:[stack visibleIndexPathsForSectionController:section2]]; + NSSet *expected2 = [NSSet setWithArray:@[ + [NSIndexPath indexPathForItem:3 inSection:0], + [NSIndexPath indexPathForItem:4 inSection:0], + [NSIndexPath indexPathForItem:5 inSection:0], + [NSIndexPath indexPathForItem:6 inSection:0], + ]]; + XCTAssertEqualObjects(visible2, expected2); + + NSSet *visible3 = [NSSet setWithArray:[stack visibleIndexPathsForSectionController:section3]]; + NSSet *expected3 = [NSSet setWithArray:@[]]; + XCTAssertEqualObjects(visible3, expected3); + + NSSet *visible4 = [NSSet setWithArray:[stack visibleIndexPathsForSectionController:section4]]; + NSSet *expected4 = [NSSet setWithArray:@[ + [NSIndexPath indexPathForItem:7 inSection:0], + [NSIndexPath indexPathForItem:8 inSection:0], + [NSIndexPath indexPathForItem:9 inSection:0], + ]]; + XCTAssertEqualObjects(visible4, expected4); + + NSSet *visible5 = [NSSet setWithArray:[stack visibleIndexPathsForSectionController:section5]]; + NSSet *expected5 = [NSSet setWithArray:@[]]; + XCTAssertEqualObjects(visible5, expected5); +} + - (void)test_whenPerformingItemUpdates_thatMutationsMapToSectionControllers { [self setupWithObjects:@[ [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]],