diff --git a/CHANGELOG.md b/CHANGELOG.md index c41adfeb7..8f36125d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,8 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag ``` ### Enhancements +- Added `viewForSupplementaryElementOfKind:atIndex:sectionController:` to `IGListAdapter`. [ryanmathews] + - Added `shouldDeselectItemAtIndex:` to `IGListSectionController` . [bladeofky](https://github.com/bladeofky) - Added `shouldSelectItemAtIndex:` to `IGListSectionController` . [dirtmelon](https://github.com/dirtmelon) diff --git a/Source/IGListKit/IGListAdapter.m b/Source/IGListKit/IGListAdapter.m index dcba39310..8ee438b45 100644 --- a/Source/IGListKit/IGListAdapter.m +++ b/Source/IGListKit/IGListAdapter.m @@ -1051,6 +1051,37 @@ - (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index return nil; } +- (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind + atIndex:(NSInteger)index + sectionController:(IGListSectionController *)sectionController { + IGAssertMainThread(); + IGParameterAssert(sectionController != nil); + + // if this is accessed while a cell is being dequeued or displaying working range elements, just return nil + if (_isDequeuingSupplementaryView || _isSendingWorkingRangeDisplayUpdates) { + return nil; + } + + NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:YES]; + // prevent querying the collection view if it isn't fully reloaded yet for the current data set + if (indexPath != nil + && indexPath.section < [self.collectionView numberOfSections]) { + // only return a supplementary view if it belongs to the section controller + UICollectionReusableView *view = [self.collectionView supplementaryViewForElementKind:elementKind atIndexPath:indexPath]; + + if (IGListExperimentEnabled(_experiments, IGListExperimentSkipViewSectionControllerMap)) { + if ([self sectionControllerForSection:indexPath.section] == sectionController) { + return view; + } + } else { + if ([self sectionControllerForView:view] == sectionController) { + return view; + } + } + } + return nil; +} + - (NSArray *)fullyVisibleCellsForSectionController:(IGListSectionController *)sectionController { const NSInteger section = [self sectionForSectionController:sectionController]; if (section == NSNotFound) { diff --git a/Source/IGListKit/IGListCollectionContext.h b/Source/IGListKit/IGListCollectionContext.h index b77ff2080..628701575 100644 --- a/Source/IGListKit/IGListCollectionContext.h +++ b/Source/IGListKit/IGListCollectionContext.h @@ -94,6 +94,21 @@ NS_SWIFT_NAME(ListCollectionContext) - (nullable __kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController; +/** + Returns the supplementary view in the collection at the specified index for the section controller. + + @param elementKind The element kind of the supplementary view. + @param index The index of the desired cell. + @param sectionController The section controller requesting this information. + + @return The collection reusable view, or `nil` if not found. + + @warning This method may return `nil` if the cell is offscreen. + */ +- (nullable __kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind + atIndex:(NSInteger)index + sectionController:(IGListSectionController *)sectionController; + /** Returns the fully visible cells for the given section controller. diff --git a/Source/IGListKit/Internal/IGListAdapter+UICollectionView.m b/Source/IGListKit/Internal/IGListAdapter+UICollectionView.m index feecd4670..70297b1fe 100644 --- a/Source/IGListKit/Internal/IGListAdapter+UICollectionView.m +++ b/Source/IGListKit/Internal/IGListAdapter+UICollectionView.m @@ -61,7 +61,12 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { IGListSectionController *sectionController = [self sectionControllerForSection:indexPath.section]; id supplementarySource = [sectionController supplementaryViewSource]; + + // flag that a supplementary view is being dequeued in case it tries to access a supplementary view in the process + _isDequeuingSupplementaryView = YES; UICollectionReusableView *view = [supplementarySource viewForSupplementaryElementOfKind:kind atIndex:indexPath.item]; + _isDequeuingSupplementaryView = NO; + IGAssert(view != nil, @"Returned a nil supplementary view at indexPath <%@> from section controller: <%@>, supplementary source: <%@>", indexPath, sectionController, supplementarySource); // associate the section controller with the cell so that we know which section controller is using it diff --git a/Source/IGListKit/Internal/IGListAdapterInternal.h b/Source/IGListKit/Internal/IGListAdapterInternal.h index 4097421ee..d4afe2671 100644 --- a/Source/IGListKit/Internal/IGListAdapterInternal.h +++ b/Source/IGListKit/Internal/IGListAdapterInternal.h @@ -30,6 +30,8 @@ IGListBatchContext { __weak UICollectionView *_collectionView; BOOL _isDequeuingCell; + BOOL _isDequeuingSupplementaryView; + BOOL _isSendingWorkingRangeDisplayUpdates; } diff --git a/Tests/IGListAdapterTests.m b/Tests/IGListAdapterTests.m index c4efcb372..0781c0ae3 100644 --- a/Tests/IGListAdapterTests.m +++ b/Tests/IGListAdapterTests.m @@ -2146,4 +2146,20 @@ - (void)test_whenSectionControllerRemoved_thatDoesNotCrashOnInvalidatingLayout { [sectionController.collectionContext invalidateLayoutForSectionController:sectionController completion:nil]; } +- (void)test_whenSettingSupplementaryView_thatViewForSupplementaryElementExists { + self.dataSource.objects = @[@0]; + [self.adapter reloadDataWithCompletion:nil]; + + IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new]; + supplementarySource.collectionContext = self.adapter; + supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionHeader]; + + IGListSectionController *controller = [self.adapter sectionControllerForObject:@0]; + controller.supplementaryViewSource = supplementarySource; + supplementarySource.sectionController = controller; + + [self.adapter performUpdatesAnimated:NO completion:nil]; + XCTAssertNotNil([self.adapter viewForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndex:0 sectionController:controller]); +} + @end