diff --git a/CHANGELOG.md b/CHANGELOG.md index e3892781..77e99c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This release closes the [2.1.0 milestone](https://github.com/Instagram/IGListKit - Disables `prefetchEnabled` by default on `IGListCollectionView`. [Sven Bacia](https://github.com/svenbacia) [(#323)](https://github.com/Instagram/IGListKit/pull/323) +- Working ranges now work with `IGListStackedSectionController`. [Ryan Nystrom](https://github.com/rnystrom) [(#356)](https://github.com/Instagram/IGListKit/pull/356) + ### Fixes - Avoid `UICollectionView` crashes when queueing a reload and insert/delete on the same item as well as reloading an item in a section that is animating. [Ryan Nystrom](https://github.com/rnystrom) [(#325)](https://github.com/Instagram/IGListKit/pull/325) diff --git a/Source/IGListStackedSectionController.m b/Source/IGListStackedSectionController.m index ec48bc28..07211858 100644 --- a/Source/IGListStackedSectionController.m +++ b/Source/IGListStackedSectionController.m @@ -56,6 +56,7 @@ static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerInde self.displayDelegate = self; self.scrollDelegate = self; + self.workingRangeDelegate = self; [self reloadData]; } @@ -363,4 +364,18 @@ static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerInde } } +#pragma mark - IGListWorkingRangeDelegate + +- (void)listAdapter:(IGListAdapter *)listAdapter sectionControllerWillEnterWorkingRange:(IGListSectionController *)sectionController { + for (IGListSectionController *childSectionController in self.sectionControllers) { + [[childSectionController workingRangeDelegate] listAdapter:listAdapter sectionControllerWillEnterWorkingRange:childSectionController]; + } +} + +- (void)listAdapter:(IGListAdapter *)listAdapter sectionControllerDidExitWorkingRange:(IGListSectionController *)sectionController { + for (IGListSectionController *childSectionController in self.sectionControllers) { + [[childSectionController workingRangeDelegate] listAdapter:listAdapter sectionControllerDidExitWorkingRange:childSectionController]; + } +} + @end diff --git a/Source/Internal/IGListStackedSectionControllerInternal.h b/Source/Internal/IGListStackedSectionControllerInternal.h index 2a5fb6fc..af10fb24 100644 --- a/Source/Internal/IGListStackedSectionControllerInternal.h +++ b/Source/Internal/IGListStackedSectionControllerInternal.h @@ -15,7 +15,8 @@ < IGListCollectionContext, IGListDisplayDelegate, -IGListScrollDelegate +IGListScrollDelegate, +IGListWorkingRangeDelegate > @property (nonatomic, strong, readonly) NSOrderedSet<__kindof IGListSectionController *> *sectionControllers; diff --git a/Tests/IGListStackSectionControllerTests.m b/Tests/IGListStackSectionControllerTests.m index f394cd1a..92ea3df2 100644 --- a/Tests/IGListStackSectionControllerTests.m +++ b/Tests/IGListStackSectionControllerTests.m @@ -54,7 +54,7 @@ static const CGRect kStackTestFrame = (CGRect){{0.0, 0.0}, {100.0, 100.0}}; self.collectionView.frame = kStackTestFrame; self.dataSource = [[IGTestStackedDataSource alloc] init]; - self.adapter = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil workingRangeSize:0]; + self.adapter = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil workingRangeSize:1]; } - (void)tearDown { @@ -702,6 +702,57 @@ static const CGRect kStackTestFrame = (CGRect){{0.0, 0.0}, {100.0, 100.0}}; XCTAssertFalse([[self.collectionView cellForItemAtIndexPath:path] isSelected]); } +- (void)test_whenRemovingSection_withWorkingRange_thatChildSectionControllersReceiveEvents { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@1 value:@[@1, @1]] + ]]; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects.firstObject]; + IGListTestSection *section = stack.sectionControllers.firstObject; + + id mockDelegate = [OCMockObject mockForProtocol:@protocol(IGListWorkingRangeDelegate)]; + [[mockDelegate expect] listAdapter:self.adapter sectionControllerDidExitWorkingRange:section]; + + section.workingRangeDelegate = mockDelegate; + + self.dataSource.objects = @[ + [[IGTestObject alloc] initWithKey:@1 value:@[@1, @1]], + ]; + + XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + [mockDelegate verify]; + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenScrolling_withWorkingRange_thatChildSectionControllersReceiveEvents { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@1 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@2 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@3 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@4 value:@[@1, @1]] + ]]; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects.lastObject]; + IGListTestSection *section = stack.sectionControllers.firstObject; + + id mockDelegate = [OCMockObject mockForProtocol:@protocol(IGListWorkingRangeDelegate)]; + [[mockDelegate expect] listAdapter:self.adapter sectionControllerWillEnterWorkingRange:section]; + + section.workingRangeDelegate = mockDelegate; + + [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:4] atScrollPosition:UICollectionViewScrollPositionTop animated:NO]; + [self.collectionView layoutIfNeeded]; + + [mockDelegate verify]; +} + - (void)test_whenRemovingCellsFromChild_thatStackSendsDisplayEventsCorrectly { IGTestObject *object = [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2]]; [self setupWithObjects:@[object]];