mirror of
https://github.com/Instagram/IGListKit
synced 2026-05-14 04:49:05 +00:00
Summary: This pull request removes the `sectionForSectionController:` method from the `IGListCollectionContext` protocol so that the protocol is exclusively for presentation methods. This should not add new functionality, but rather makes the index directly accessible on the section controllers themselves. This change makes sense because at no time will there be an update to the list that the list adapter is unaware of and so it will always be able to set and update any indexes for a section controller that has changed. Issue fixed: #609 - [X] All tests pass. Demo project builds and runs. - [X] I added tests, an experiment, or detailed why my change isn't tested. - [X] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes. - [X] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md) Closes https://github.com/Instagram/IGListKit/pull/671 Reviewed By: jessesquires Differential Revision: D4942159 Pulled By: amonshiz fbshipit-source-id: d648cfdd381cbf1d9ee7ff549ae27d2972a84622
419 lines
22 KiB
Objective-C
419 lines
22 KiB
Objective-C
/**
|
|
* Copyright (c) 2016-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "IGListStackedSectionControllerInternal.h"
|
|
|
|
#import <objc/runtime.h>
|
|
|
|
#import <IGListKit/IGListAssert.h>
|
|
#import <IGListKit/IGListSupplementaryViewSource.h>
|
|
|
|
#import "IGListSectionControllerInternal.h"
|
|
|
|
@interface UICollectionViewCell (IGListStackedSectionController)
|
|
@end
|
|
@implementation UICollectionViewCell (IGListStackedSectionController)
|
|
|
|
static void * kStackedSectionControllerKey = &kStackedSectionControllerKey;
|
|
|
|
- (void)ig_setStackedSectionController:(id)stackedSectionController {
|
|
objc_setAssociatedObject(self, kStackedSectionControllerKey, stackedSectionController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (id)ig_stackedSectionController {
|
|
return objc_getAssociatedObject(self, kStackedSectionControllerKey);
|
|
}
|
|
|
|
static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerIndexKey;
|
|
|
|
- (void)ig_setStackedSectionControllerIndex:(NSInteger)stackedSectionControllerIndex {
|
|
objc_setAssociatedObject(self, kStackedSectionControllerIndexKey, @(stackedSectionControllerIndex), OBJC_ASSOCIATION_COPY_NONATOMIC);
|
|
}
|
|
|
|
- (NSInteger)ig_stackedSectionControllerIndex {
|
|
return [objc_getAssociatedObject(self, kStackedSectionControllerIndexKey) integerValue];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation IGListStackedSectionController
|
|
|
|
- (instancetype)initWithSectionControllers:(NSArray <IGListSectionController *> *)sectionControllers {
|
|
if (self = [super init]) {
|
|
for (IGListSectionController *sectionController in sectionControllers) {
|
|
sectionController.collectionContext = self;
|
|
sectionController.viewController = self.viewController;
|
|
}
|
|
|
|
_visibleSectionControllers = [[NSCountedSet alloc] init];
|
|
_sectionControllers = [NSOrderedSet orderedSetWithArray:sectionControllers];
|
|
|
|
self.displayDelegate = self;
|
|
self.scrollDelegate = self;
|
|
self.workingRangeDelegate = self;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
|
|
#pragma mark - Private API
|
|
|
|
- (void)reloadData {
|
|
NSMutableArray *sectionControllers = [[NSMutableArray alloc] init];
|
|
NSMutableArray *offsets = [[NSMutableArray alloc] init];
|
|
|
|
NSInteger numberOfItems = 0;
|
|
for (IGListSectionController *sectionController in self.sectionControllers) {
|
|
[offsets addObject:@(numberOfItems)];
|
|
|
|
const NSInteger items = [sectionController numberOfItems];
|
|
for (NSInteger i = 0; i < items; i++) {
|
|
[sectionControllers addObject:sectionController];
|
|
}
|
|
|
|
numberOfItems += items;
|
|
}
|
|
|
|
self.sectionControllerOffsets = offsets;
|
|
self.flattenedNumberOfItems = numberOfItems;
|
|
self.sectionControllersForItems = sectionControllers;
|
|
|
|
IGAssert(self.sectionControllerOffsets.count == self.sectionControllers.count, @"Not enough offsets for section controllers");
|
|
IGAssert(self.sectionControllersForItems.count == self.flattenedNumberOfItems, @"Controller map does not equal total number of items");
|
|
}
|
|
|
|
- (IGListSectionController *)sectionControllerForObjectIndex:(NSInteger)itemIndex {
|
|
return self.sectionControllersForItems[itemIndex];
|
|
}
|
|
|
|
- (NSInteger)offsetForSectionController:(IGListSectionController *)sectionController {
|
|
const NSInteger index = [self.sectionControllers indexOfObject:sectionController];
|
|
IGAssert(index != NSNotFound, @"Querying offset for an undocumented section controller");
|
|
return [self.sectionControllerOffsets[index] integerValue];
|
|
}
|
|
|
|
- (NSInteger)localIndexForSectionController:(IGListSectionController *)sectionController index:(NSInteger)index {
|
|
const NSInteger offset = [self offsetForSectionController:sectionController];
|
|
IGAssert(offset <= index, @"Section controller offset must be less than or equal to the item index");
|
|
return index - offset;
|
|
}
|
|
|
|
- (NSInteger)relativeIndexForSectionController:(IGListSectionController *)sectionController fromLocalIndex:(NSInteger)index {
|
|
const NSInteger offset = [self offsetForSectionController:sectionController];
|
|
return index + offset;
|
|
}
|
|
|
|
- (NSIndexSet *)itemIndexesForSectionController:(IGListSectionController *)sectionController indexes:(NSIndexSet *)indexes {
|
|
const NSInteger offset = [self offsetForSectionController:sectionController];
|
|
NSMutableIndexSet *itemIndexes = [[NSMutableIndexSet alloc] init];
|
|
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
|
[itemIndexes addIndex:(idx + offset)];
|
|
}];
|
|
return itemIndexes;
|
|
}
|
|
|
|
- (id<IGListSupplementaryViewSource>)supplementaryViewSource {
|
|
for (IGListSectionController *sectionController in self.sectionControllers) {
|
|
id<IGListSupplementaryViewSource> supplementaryViewSource = sectionController.supplementaryViewSource;
|
|
if (supplementaryViewSource != nil) {
|
|
return supplementaryViewSource;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
#pragma mark - IGListSectionController Overrides
|
|
|
|
- (NSInteger)numberOfItems {
|
|
return self.flattenedNumberOfItems;
|
|
}
|
|
|
|
- (CGSize)sizeForItemAtIndex:(NSInteger)index {
|
|
IGListSectionController *sectionController = [self sectionControllerForObjectIndex:index];
|
|
const NSInteger localIndex = [self localIndexForSectionController:sectionController index:index];
|
|
return [sectionController sizeForItemAtIndex:localIndex];
|
|
}
|
|
|
|
- (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index {
|
|
IGListSectionController *sectionController = [self sectionControllerForObjectIndex:index];
|
|
const NSInteger localIndex = [self localIndexForSectionController:sectionController index:index];
|
|
return [sectionController cellForItemAtIndex:localIndex];
|
|
}
|
|
|
|
- (void)didUpdateToObject:(id)object {
|
|
for (IGListSectionController *sectionController in self.sectionControllers) {
|
|
sectionController.sectionIndex = self.sectionIndex;
|
|
[sectionController didUpdateToObject:object];
|
|
}
|
|
[self reloadData];
|
|
}
|
|
|
|
- (void)didSelectItemAtIndex:(NSInteger)index {
|
|
IGListSectionController *sectionController = [self sectionControllerForObjectIndex:index];
|
|
const NSInteger localIndex = [self localIndexForSectionController:sectionController index:index];
|
|
[sectionController didSelectItemAtIndex:localIndex];
|
|
}
|
|
|
|
#pragma mark - IGListCollectionContext
|
|
|
|
- (CGSize)containerSize {
|
|
return [self.collectionContext containerSize];
|
|
}
|
|
|
|
- (UIEdgeInsets)containerInset {
|
|
return [self.collectionContext containerInset];
|
|
}
|
|
|
|
- (CGSize)insetContainerSize {
|
|
return [self.collectionContext insetContainerSize];
|
|
}
|
|
|
|
- (CGSize)containerSizeForSectionController:(IGListSectionController *)sectionController {
|
|
const UIEdgeInsets inset = sectionController.inset;
|
|
return CGSizeMake(self.containerSize.width - inset.left - inset.right,
|
|
self.containerSize.height - inset.top - inset.bottom);
|
|
}
|
|
|
|
- (NSInteger)indexForCell:(UICollectionViewCell *)cell sectionController:(IGListSectionController *)sectionController {
|
|
const NSInteger index = [self.collectionContext indexForCell:cell sectionController:self];
|
|
return [self localIndexForSectionController:sectionController index:index];
|
|
}
|
|
|
|
- (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController {
|
|
return [self.collectionContext cellForItemAtIndex: [self relativeIndexForSectionController:sectionController fromLocalIndex:index] sectionController:self];
|
|
}
|
|
|
|
- (NSArray<UICollectionViewCell *> *)visibleCellsForSectionController:(IGListSectionController *)sectionController {
|
|
NSMutableArray *cells = [NSMutableArray new];
|
|
id<IGListCollectionContext> collectionContext = self.collectionContext;
|
|
NSArray *visibleCells = [collectionContext visibleCellsForSectionController:self];
|
|
for (UICollectionViewCell *cell in visibleCells) {
|
|
const NSInteger index = [collectionContext indexForCell:cell sectionController:self];
|
|
if (self.sectionControllersForItems[index] == sectionController) {
|
|
[cells addObject:cell];
|
|
}
|
|
}
|
|
return cells;
|
|
}
|
|
|
|
- (NSArray<NSIndexPath *> *)visibleIndexPathsForSectionController:(IGListSectionController *)sectionController {
|
|
NSMutableArray *paths = [NSMutableArray new];
|
|
id<IGListCollectionContext> 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];
|
|
}
|
|
|
|
- (UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass
|
|
forSectionController:(IGListSectionController *)sectionController
|
|
atIndex:(NSInteger)index {
|
|
const NSInteger offsetIndex = [self relativeIndexForSectionController:sectionController fromLocalIndex:index];
|
|
return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableCellOfClass:cellClass
|
|
forSectionController:self
|
|
atIndex:offsetIndex];
|
|
}
|
|
|
|
- (UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName
|
|
bundle:(NSBundle *)bundle
|
|
forSectionController:(IGListSectionController *)sectionController
|
|
atIndex:(NSInteger)index {
|
|
const NSInteger offsetIndex = [self relativeIndexForSectionController:sectionController fromLocalIndex:index];
|
|
return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableCellWithNibName:nibName
|
|
bundle:bundle
|
|
forSectionController:self
|
|
atIndex:offsetIndex];
|
|
}
|
|
|
|
- (UICollectionViewCell *)dequeueReusableCellFromStoryboardWithIdentifier:(NSString *)identifier
|
|
forSectionController:(IGListSectionController *)sectionController
|
|
atIndex:(NSInteger)index {
|
|
const NSInteger offsetIndex = [self relativeIndexForSectionController:sectionController fromLocalIndex:index];
|
|
return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableCellFromStoryboardWithIdentifier:identifier
|
|
forSectionController:self
|
|
atIndex:offsetIndex];
|
|
}
|
|
|
|
- (UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind
|
|
forSectionController:(IGListSectionController *)sectionController
|
|
class:(Class)viewClass
|
|
atIndex:(NSInteger)index {
|
|
const NSInteger offsetIndex = [self relativeIndexForSectionController:sectionController fromLocalIndex:index];
|
|
return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableSupplementaryViewOfKind:elementKind
|
|
forSectionController:self
|
|
class:viewClass
|
|
atIndex:offsetIndex];
|
|
}
|
|
|
|
- (UICollectionReusableView *)dequeueReusableSupplementaryViewFromStoryboardOfKind:(NSString *)elementKind
|
|
withIdentifier:(NSString *)identifier
|
|
forSectionController:(IGListSectionController *)sectionController
|
|
atIndex:(NSInteger)index {
|
|
const NSInteger offsetIndex = [self relativeIndexForSectionController:sectionController fromLocalIndex:index];
|
|
return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableSupplementaryViewFromStoryboardOfKind:elementKind
|
|
withIdentifier:identifier
|
|
forSectionController:self
|
|
atIndex:offsetIndex];
|
|
}
|
|
|
|
- (UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind
|
|
forSectionController:(IGListSectionController *)sectionController
|
|
nibName:(NSString *)nibName
|
|
bundle:(NSBundle *)bundle
|
|
atIndex:(NSInteger)index {
|
|
const NSInteger offsetIndex = [self relativeIndexForSectionController:sectionController fromLocalIndex:index];
|
|
return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableSupplementaryViewOfKind:elementKind
|
|
forSectionController:self
|
|
nibName:nibName
|
|
bundle:bundle
|
|
atIndex:offsetIndex];
|
|
}
|
|
|
|
- (void)performBatchAnimated:(BOOL)animated updates:(void (^)(id<IGListBatchContext>))updates completion:(void (^)(BOOL))completion {
|
|
__weak __typeof__(self) weakSelf = self;
|
|
[self.collectionContext performBatchAnimated:animated updates:^ (id<IGListBatchContext> batchContext) {
|
|
weakSelf.forwardingBatchContext = batchContext;
|
|
updates(weakSelf);
|
|
weakSelf.forwardingBatchContext = nil;
|
|
} completion:^(BOOL finished) {
|
|
[weakSelf reloadData];
|
|
if (completion) {
|
|
completion(finished);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)scrollToSectionController:(IGListSectionController *)sectionController
|
|
atIndex:(NSInteger)index
|
|
scrollPosition:(UICollectionViewScrollPosition)scrollPosition
|
|
animated:(BOOL)animated {
|
|
const NSInteger offsetIndex = [self relativeIndexForSectionController:sectionController fromLocalIndex:index];
|
|
[self.collectionContext scrollToSectionController:self
|
|
atIndex:offsetIndex
|
|
scrollPosition:scrollPosition
|
|
animated:animated];
|
|
}
|
|
|
|
- (void)invalidateLayoutForSectionController:(IGListSectionController *)sectionController completion:(void (^)(BOOL))completion {
|
|
[self.collectionContext invalidateLayoutForSectionController:self completion:completion];
|
|
}
|
|
|
|
#pragma mark - IGListBatchContext
|
|
|
|
- (void)reloadInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes {
|
|
NSIndexSet *itemIndexes = [self itemIndexesForSectionController:sectionController indexes:indexes];
|
|
[self.forwardingBatchContext reloadInSectionController:self atIndexes:itemIndexes];
|
|
}
|
|
|
|
- (void)insertInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes {
|
|
[self reloadData];
|
|
NSIndexSet *itemIndexes = [self itemIndexesForSectionController:sectionController indexes:indexes];
|
|
[self.forwardingBatchContext insertInSectionController:self atIndexes:itemIndexes];
|
|
}
|
|
|
|
- (void)deleteInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes {
|
|
[self reloadData];
|
|
NSIndexSet *itemIndexes = [self itemIndexesForSectionController:sectionController indexes:indexes];
|
|
[self.forwardingBatchContext deleteInSectionController:self atIndexes:itemIndexes];
|
|
}
|
|
|
|
- (void)moveInSectionController:(IGListSectionController *)sectionController fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
|
|
[self reloadData];
|
|
const NSInteger fromRelativeIndex = [self relativeIndexForSectionController:sectionController fromLocalIndex:fromIndex];
|
|
const NSInteger toRelativeIndex = [self relativeIndexForSectionController:sectionController fromLocalIndex:toIndex];
|
|
[self.forwardingBatchContext moveInSectionController:self fromIndex:fromRelativeIndex toIndex:toRelativeIndex];
|
|
}
|
|
|
|
- (void)reloadSectionController:(IGListSectionController *)sectionController {
|
|
[self reloadData];
|
|
[self.forwardingBatchContext reloadSectionController:self];
|
|
}
|
|
|
|
#pragma mark - IGListDisplayDelegate
|
|
|
|
- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController cell:(UICollectionViewCell *)cell atIndex:(NSInteger)index {
|
|
IGListSectionController *childSectionController = [self sectionControllerForObjectIndex:index];
|
|
const NSInteger localIndex = [self localIndexForSectionController:childSectionController index:index];
|
|
|
|
// update the assoc objects for use in didEndDisplay
|
|
[cell ig_setStackedSectionController:childSectionController];
|
|
[cell ig_setStackedSectionControllerIndex:localIndex];
|
|
|
|
NSCountedSet *visibleSectionControllers = self.visibleSectionControllers;
|
|
id<IGListDisplayDelegate> displayDelegate = [childSectionController displayDelegate];
|
|
|
|
if ([visibleSectionControllers countForObject:childSectionController] == 0) {
|
|
[displayDelegate listAdapter:listAdapter willDisplaySectionController:childSectionController];
|
|
}
|
|
[displayDelegate listAdapter:listAdapter willDisplaySectionController:childSectionController cell:cell atIndex:localIndex];
|
|
|
|
[visibleSectionControllers addObject:childSectionController];
|
|
}
|
|
|
|
- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController cell:(UICollectionViewCell *)cell atIndex:(NSInteger)index {
|
|
const NSInteger localIndex = [cell ig_stackedSectionControllerIndex];
|
|
IGListSectionController *childSectionController = [cell ig_stackedSectionController];
|
|
|
|
NSCountedSet *visibleSectionControllers = self.visibleSectionControllers;
|
|
id<IGListDisplayDelegate> displayDelegate = [childSectionController displayDelegate];
|
|
|
|
[displayDelegate listAdapter:listAdapter didEndDisplayingSectionController:childSectionController cell:cell atIndex:localIndex];
|
|
|
|
[visibleSectionControllers removeObject:childSectionController];
|
|
if ([visibleSectionControllers countForObject:childSectionController] == 0) {
|
|
[displayDelegate listAdapter:listAdapter didEndDisplayingSectionController:childSectionController];
|
|
}
|
|
}
|
|
|
|
- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController {}
|
|
- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController {}
|
|
|
|
#pragma mark - IGListScrollDelegate
|
|
|
|
- (void)listAdapter:(IGListAdapter *)listAdapter didScrollSectionController:(IGListSectionController *)sectionController {
|
|
for (IGListSectionController *childSectionController in self.sectionControllers) {
|
|
[[childSectionController scrollDelegate] listAdapter:listAdapter didScrollSectionController:childSectionController];
|
|
}
|
|
}
|
|
|
|
- (void)listAdapter:(IGListAdapter *)listAdapter willBeginDraggingSectionController:(IGListSectionController *)sectionController {
|
|
for (IGListSectionController *childSectionController in self.sectionControllers) {
|
|
[[childSectionController scrollDelegate] listAdapter:listAdapter willBeginDraggingSectionController:childSectionController];
|
|
}
|
|
}
|
|
|
|
- (void)listAdapter:(IGListAdapter *)listAdapter didEndDraggingSectionController:(IGListSectionController *)sectionController willDecelerate:(BOOL)decelerate {
|
|
for (IGListSectionController *childSectionController in self.sectionControllers) {
|
|
[[childSectionController scrollDelegate] listAdapter:listAdapter didEndDraggingSectionController:childSectionController willDecelerate:decelerate];
|
|
}
|
|
}
|
|
|
|
#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
|