Skip to content

Commit

Permalink
ship IGListExperimentAvoidLayoutOnScrollToObject
Browse files Browse the repository at this point in the history
Summary: Remove `[collectionView layoutIfNeeded]` before scrolling in `[IGListAdapter scrollToObject...]` to avoid creating off-screen cells.

Reviewed By: apadalko

Differential Revision: D21822496

fbshipit-source-id: 3251beed489c1e3e8d7a872638e37a5580ec0f4f
  • Loading branch information
maxolls authored and facebook-github-bot committed Jun 2, 2020
1 parent 677ce77 commit ea03bc9
Show file tree
Hide file tree
Showing 4 changed files with 11 additions and 131 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag

- `IGListCollectionViewLayout` should get the section/index counts via `UICollectionView` to stay in sync, instead of the `dataSource` [Maxime Ollivier](https://github.com/maxolls) (tbd)

- Remove `[collectionView layoutIfNeeded]` before scrolling in `[IGListAdapter scrollToObject...]` to avoid creating off-screen cells. [Maxime Ollivier](https://github.com/maxolls) (tbd)

4.0.0
-----
### Breaking Changes
Expand Down
2 changes: 0 additions & 2 deletions Source/IGListDiffKit/IGListExperiments.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ typedef NS_OPTIONS (NSInteger, IGListExperiment) {
IGListExperimentBackgroundDiffing = 1 << 2,
/// Test fallback to reloadData when "too many" update operations.
IGListExperimentReloadDataFallback = 1 << 3,
/// Test removing the layout pass when calling scrollToObject to avoid creating off-screen cells.
IGListExperimentAvoidLayoutOnScrollToObject = 1 << 4,
/// Test fixing a crash when inserting and deleting the same NSIndexPath multiple times.
IGListExperimentFixIndexPathImbalance = 1 << 5,
/// Test deferring object creation until just before diffing.
Expand Down
37 changes: 9 additions & 28 deletions Source/IGListKit/IGListAdapter.m
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,10 @@ - (void)scrollToObject:(id)object
}

UICollectionView *collectionView = self.collectionView;
const BOOL avoidLayout = IGListExperimentEnabled(self.experiments, IGListExperimentAvoidLayoutOnScrollToObject);

// Experiment with skipping the forced layout to avoid creating off-screen cells.
// Calling [collectionView layoutIfNeeded] creates the current visible cells that will no longer be visible after the scroll.
// We can avoid that by asking the UICollectionView (not the layout object) for the attributes. So if the attributes are not
// ready, the UICollectionView will call -prepareLayout, return the attributes, but doesn't generate the cells just yet.
if (!avoidLayout) {
[collectionView setNeedsLayout];
[collectionView layoutIfNeeded];
}

// We avoid calling `[collectionView layoutIfNeeded]` here because that could create cells that will no longer be visible after the scroll.
// Note that we get the layout attributes from the `UICollectionView` instead of the `collectionViewLayout`, because that will generate the
// necessary attributes without creating the cells just yet.

NSIndexPath *indexPathFirstElement = [NSIndexPath indexPathForItem:0 inSection:section];

Expand Down Expand Up @@ -300,14 +294,9 @@ - (void)scrollToObject:(id)object
contentOffset.y = offsetMin - contentInset.top;
break;
}
CGFloat maxHeight;
if (avoidLayout) {
// If we don't call [collectionView layoutIfNeeded], the collectionView.contentSize does not get updated.
// So lets use the layout object, since it should have been updated by now.
maxHeight = collectionView.collectionViewLayout.collectionViewContentSize.height;
} else {
maxHeight = collectionView.contentSize.height;
}
// If we don't call [collectionView layoutIfNeeded], the collectionView.contentSize does not get updated.
// So lets use the layout object, since it should have been updated by now.
const CGFloat maxHeight = collectionView.collectionViewLayout.collectionViewContentSize.height;
const CGFloat maxOffsetY = maxHeight - collectionView.frame.size.height + contentInset.bottom;
const CGFloat minOffsetY = -contentInset.top;
contentOffset.y = MIN(contentOffset.y, maxOffsetY);
Expand Down Expand Up @@ -752,20 +741,12 @@ - (NSIndexPath *)indexPathForSectionController:(IGListSectionController *)contro
}

- (nullable UICollectionViewLayoutAttributes *)_layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
if (IGListExperimentEnabled(self.experiments, IGListExperimentAvoidLayoutOnScrollToObject)) {
return [self.collectionView layoutAttributesForItemAtIndexPath:indexPath];
} else {
return [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
}
return [self.collectionView layoutAttributesForItemAtIndexPath:indexPath];
}

- (nullable UICollectionViewLayoutAttributes *)_layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind
atIndexPath:(NSIndexPath *)indexPath {
if (IGListExperimentEnabled(self.experiments, IGListExperimentAvoidLayoutOnScrollToObject)) {
return [self.collectionView layoutAttributesForSupplementaryElementOfKind:elementKind atIndexPath:indexPath];
} else {
return [self.collectionView.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath];
}
return [self.collectionView layoutAttributesForSupplementaryElementOfKind:elementKind atIndexPath:indexPath];
}

- (void)mapView:(UICollectionReusableView *)view toSectionController:(IGListSectionController *)sectionController {
Expand Down
101 changes: 0 additions & 101 deletions Tests/IGListAdapterTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -655,15 +655,6 @@ - (void)test_whenAdapterUpdated_thatVisibleCellsForNilObjectIsEmpty {
}

- (void)test_whenScrollVerticallyToItem {
[self performTest_whenScrollVerticallyToItem];
}

- (void)test_whenScrollVerticallyToItem_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollVerticallyToItem];
}

- (void)performTest_whenScrollVerticallyToItem {
// # of items for each object == [item integerValue], so @2 has 2 items (cells)
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
Expand All @@ -684,15 +675,6 @@ - (void)performTest_whenScrollVerticallyToItem {
}

- (void)test_whenScrollVerticallyToItemInASectionWithNoCellsAndNoSupplymentaryView {
[self performTest_whenScrollVerticallyToItemInASectionWithNoCellsAndNoSupplymentaryView];
}

- (void)test_whenScrollVerticallyToItemInASectionWithNoCellsAndNoSupplymentaryView_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollVerticallyToItemInASectionWithNoCellsAndNoSupplymentaryView];
}

- (void)performTest_whenScrollVerticallyToItemInASectionWithNoCellsAndNoSupplymentaryView {
self.dataSource.objects = @[@1, @0, @300];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 3);
Expand All @@ -705,15 +687,6 @@ - (void)performTest_whenScrollVerticallyToItemInASectionWithNoCellsAndNoSupplyme
}

- (void)test_whenScrollVerticallyToItemInASectionWithNoCellsButAHeaderSupplymentaryView {
[self performTest_whenScrollVerticallyToItemInASectionWithNoCellsButAHeaderSupplymentaryView];
}

- (void)test_whenScrollVerticallyToItemInASectionWithNoCellsButAHeaderSupplymentaryView_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollVerticallyToItemInASectionWithNoCellsButAHeaderSupplymentaryView];
}

- (void)performTest_whenScrollVerticallyToItemInASectionWithNoCellsButAHeaderSupplymentaryView {
self.dataSource.objects = @[@1, @0, @300];
[self.adapter reloadDataWithCompletion:nil];

Expand All @@ -739,15 +712,6 @@ - (void)performTest_whenScrollVerticallyToItemInASectionWithNoCellsButAHeaderSup
}

- (void)test_whenScrollVerticallyToItemWithPositionning {
[self performTest_whenScrollVerticallyToItemWithPositionning];
}

- (void)test_whenScrollVerticallyToItemWithPositionning_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollVerticallyToItemWithPositionning];
}

- (void)performTest_whenScrollVerticallyToItemWithPositionning {
self.dataSource.objects = @[@1, @100, @200];
[self.adapter reloadDataWithCompletion:nil];
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
Expand All @@ -773,15 +737,6 @@ - (void)performTest_whenScrollVerticallyToItemWithPositionning {
}

- (void)test_whenScrollVerticallyToBottom_withContentInsets_thatBottomFlushWithCollectionViewBounds {
[self performTest_whenScrollVerticallyToBottom_withContentInsets_thatBottomFlushWithCollectionViewBounds];
}

- (void)test_whenScrollVerticallyToBottom_withContentInsets_thatBottomFlushWithCollectionViewBounds_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollVerticallyToBottom_withContentInsets_thatBottomFlushWithCollectionViewBounds];
}

- (void)performTest_whenScrollVerticallyToBottom_withContentInsets_thatBottomFlushWithCollectionViewBounds {
self.dataSource.objects = @[@100];
[self.adapter reloadDataWithCompletion:nil];

Expand Down Expand Up @@ -810,15 +765,6 @@ - (void)performTest_whenScrollVerticallyToBottom_withContentInsets_thatBottomFlu
}

- (void)test_whenScrollHorizontalToRight_withContentInsets_thatRightFlushWithCollectionViewBounds {
[self performTest_whenScrollHorizontalToRight_withContentInsets_thatRightFlushWithCollectionViewBounds];
}

- (void)test_whenScrollHorizontalToRight_withContentInsets_thatRightFlushWithCollectionViewBounds_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollHorizontalToRight_withContentInsets_thatRightFlushWithCollectionViewBounds];
}

- (void)performTest_whenScrollHorizontalToRight_withContentInsets_thatRightFlushWithCollectionViewBounds {
self.dataSource.objects = @[@100];
[self.adapter reloadDataWithCompletion:nil];

Expand Down Expand Up @@ -851,15 +797,6 @@ - (void)performTest_whenScrollHorizontalToRight_withContentInsets_thatRightFlush
}

- (void)test_whenScrollHorizontallyToItem {
[self performTest_whenScrollHorizontallyToItem];
}

- (void)test_whenScrollHorizontallyToItem_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollHorizontallyToItem];
}

- (void)performTest_whenScrollHorizontallyToItem {
// # of items for each object == [item integerValue], so @2 has 2 items (cells)
IGListTestAdapterHorizontalDataSource *dataSource = [[IGListTestAdapterHorizontalDataSource alloc] init];
self.adapter.dataSource = dataSource;
Expand All @@ -885,15 +822,6 @@ - (void)performTest_whenScrollHorizontallyToItem {
}

- (void)test_whenScrollToItem_thatSupplementarySourceSupportsSingleHeader {
[self performTest_whenScrollToItem_thatSupplementarySourceSupportsSingleHeader];
}

- (void)test_whenScrollToItem_thatSupplementarySourceSupportsSingleHeader_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollToItem_thatSupplementarySourceSupportsSingleHeader];
}

- (void)performTest_whenScrollToItem_thatSupplementarySourceSupportsSingleHeader {
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];

Expand All @@ -916,15 +844,6 @@ - (void)performTest_whenScrollToItem_thatSupplementarySourceSupportsSingleHeader
}

- (void)test_whenScrollToItem_thatSupplementarySourceSupportsHeaderAndFooter {
[self performTest_whenScrollToItem_thatSupplementarySourceSupportsHeaderAndFooter];
}

- (void)test_whenScrollToItem_thatSupplementarySourceSupportsHeaderAndFooter_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollToItem_thatSupplementarySourceSupportsHeaderAndFooter];
}

- (void)performTest_whenScrollToItem_thatSupplementarySourceSupportsHeaderAndFooter {
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];

Expand All @@ -948,15 +867,6 @@ - (void)performTest_whenScrollToItem_thatSupplementarySourceSupportsHeaderAndFoo
}

- (void)test_whenScrollVerticallyToItem_thatFeedIsEmpty {
[self performTest_whenScrollVerticallyToItem_thatFeedIsEmpty];
}

- (void)test_whenScrollVerticallyToItem_thatFeedIsEmpty_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollVerticallyToItem_thatFeedIsEmpty];
}

- (void)performTest_whenScrollVerticallyToItem_thatFeedIsEmpty {
self.dataSource.objects = @[];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 0);
Expand All @@ -965,15 +875,6 @@ - (void)performTest_whenScrollVerticallyToItem_thatFeedIsEmpty {
}

- (void)test_whenScrollVerticallyToItem_thatItemNotInFeed {
[self performTest_whenScrollVerticallyToItem_thatItemNotInFeed];
}

- (void)test_whenScrollVerticallyToItem_thatItemNotInFeed_withExperiment {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;
[self performTest_whenScrollVerticallyToItem_thatItemNotInFeed];
}

- (void)performTest_whenScrollVerticallyToItem_thatItemNotInFeed {
// # of items for each object == [item integerValue], so @2 has 2 items (cells)
self.dataSource.objects = @[@1, @2, @3, @4];
[self.adapter reloadDataWithCompletion:nil];
Expand All @@ -991,8 +892,6 @@ - (void)performTest_whenScrollVerticallyToItem_thatItemNotInFeed {
}

- (void)test_whenScrollToItem_thatNonVisibleCellsDidNotAppear {
self.adapter.experiments = self.adapter.experiments | IGListExperimentAvoidLayoutOnScrollToObject;

// Regenerate the source with existing objects
self.dataSource = [IGListTestAdapterDataSource new];
self.dataSource.objects = @[@20, @22];
Expand Down

0 comments on commit ea03bc9

Please sign in to comment.