Get index path or scroll position of first visible item in list (#1544)

Summary:
Pull Request resolved: https://github.com/Instagram/IGListKit/pull/1544

Refactored and added functions to IGListAdapter for getting index path or scroll position of first visible item in list.

This makes it possible to see which item is the current first visible item scrolled to in the list, and determine how far it is scrolled into (analogous to additionalOffset in scrollToObject:).

Reviewed By: lorixx

Differential Revision: D32196398

fbshipit-source-id: d809d5f96bb4e1d95055dbebe4e55c1a428a40ed
This commit is contained in:
Dustin Williams 2021-11-09 00:02:23 -08:00 committed by Facebook GitHub Bot
parent 05dd3cb71e
commit 987055532a
3 changed files with 367 additions and 73 deletions

View file

@ -256,7 +256,7 @@ NS_SWIFT_NAME(ListAdapter)
@param scrollPosition An option that specifies where the item should be positioned when scrolling finishes.
@param additionalOffset Additional offset amount from the scroll position.
@param animated A flag indicating if the scrolling should be animated.
@note The additional offset amount is to shift the final scroll position by some horizontal or vertical amount
depending on the scroll direction. This is necessary when scrolling to an object on a view with sticky headers, since
the sticky header would otherwise cover the top portion of the object.
@ -268,6 +268,27 @@ NS_SWIFT_NAME(ListAdapter)
additionalOffset:(CGFloat)additionalOffset
animated:(BOOL)animated;
/**
Returns the index path for the first visible cell that has been scrolled to.
This refers to the cell currently at the top/left (0, 0) of the collection view's frame,
inset by the collection view's contentInset top or left value if defined.
@return The index path of the cell or nil if not found.
*/
- (nullable NSIndexPath *)indexPathForFirstVisibleItem;
/**
Gets the scroll offset of the first visible cell scrolled into in the collection view.
This refers to the cell currently at the top/left (0, 0) of the collection view's frame,
inset by the collection view's contentInset top or left value if defined.
@param scrollDirection An option indicating the direction to scroll.
@return additionalOffset is the offset amount the first visible cell is shifted from the start of the cell,
the amount it has been scrolled into in the coordinates of the cell's bounds.
*/
- (CGFloat)offsetForFirstVisibleItemWithScrollDirection:(UICollectionViewScrollDirection)scrollDirection;
/**
Returns the size of a cell at the specified index path.

View file

@ -23,6 +23,11 @@
#import "UICollectionViewLayout+InteractiveReordering.h"
#import "UIScrollView+IGListKit.h"
typedef struct OffsetRange {
CGFloat min;
CGFloat max;
} OffsetRange;
@implementation IGListAdapter {
NSMapTable<UICollectionReusableView *, IGListSectionController *> *_viewSectionControllerMap;
// An array of blocks to execute once batch updates are finished
@ -200,7 +205,7 @@
#pragma mark - Scrolling
- (void)scrollToObject:(id)object
supplementaryKinds:(NSArray<NSString *> *)supplementaryKinds
supplementaryKinds:(nullable NSArray<NSString *> *)supplementaryKinds
scrollDirection:(UICollectionViewScrollDirection)scrollDirection
scrollPosition:(UICollectionViewScrollPosition)scrollPosition
additionalOffset:(CGFloat)additionalOffset
@ -221,13 +226,108 @@
NSIndexPath *indexPathFirstElement = [NSIndexPath indexPathForItem:0 inSection:section];
const OffsetRange offset = [self _offsetRangeForIndexPath:indexPathFirstElement
supplementaryKinds:supplementaryKinds
scrollDirection:scrollDirection];
const CGFloat offsetMid = (offset.min + offset.max) / 2.0;
const CGFloat collectionViewWidth = collectionView.bounds.size.width;
const CGFloat collectionViewHeight = collectionView.bounds.size.height;
const UIEdgeInsets contentInset = collectionView.ig_contentInset;
CGPoint contentOffset = collectionView.contentOffset;
switch (scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
switch (scrollPosition) {
case UICollectionViewScrollPositionRight:
contentOffset.x = offset.max - collectionViewWidth + contentInset.right;
break;
case UICollectionViewScrollPositionCenteredHorizontally: {
const CGFloat insets = (contentInset.left - contentInset.right) / 2.0;
contentOffset.x = offsetMid - collectionViewWidth / 2.0 - insets;
break;
}
case UICollectionViewScrollPositionLeft:
case UICollectionViewScrollPositionNone:
case UICollectionViewScrollPositionTop:
case UICollectionViewScrollPositionBottom:
case UICollectionViewScrollPositionCenteredVertically:
contentOffset.x = offset.min - contentInset.left;
break;
}
const CGFloat maxOffsetX = collectionView.contentSize.width - collectionView.frame.size.width + contentInset.right;
const CGFloat minOffsetX = -contentInset.left;
contentOffset.x += additionalOffset;
contentOffset.x = MIN(contentOffset.x, maxOffsetX);
contentOffset.x = MAX(contentOffset.x, minOffsetX);
break;
}
case UICollectionViewScrollDirectionVertical: {
switch (scrollPosition) {
case UICollectionViewScrollPositionBottom:
contentOffset.y = offset.max - collectionViewHeight + contentInset.bottom;
break;
case UICollectionViewScrollPositionCenteredVertically: {
const CGFloat insets = (contentInset.top - contentInset.bottom) / 2.0;
contentOffset.y = offsetMid - collectionViewHeight / 2.0 - insets;
break;
}
case UICollectionViewScrollPositionTop:
case UICollectionViewScrollPositionNone:
case UICollectionViewScrollPositionLeft:
case UICollectionViewScrollPositionRight:
case UICollectionViewScrollPositionCenteredHorizontally:
contentOffset.y = offset.min - contentInset.top;
break;
}
// 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 += additionalOffset;
contentOffset.y = MIN(contentOffset.y, maxOffsetY);
contentOffset.y = MAX(contentOffset.y, minOffsetY);
break;
}
}
[collectionView setContentOffset:contentOffset animated:animated];
}
- (nullable NSIndexPath *)indexPathForFirstVisibleItem {
const CGPoint contentOffset = self.collectionView.contentOffset;
const UIEdgeInsets contentInset = self.collectionView.contentInset;
const CGPoint point = CGPointMake(contentOffset.x + contentInset.left, contentOffset.y + contentInset.top);
return [self.collectionView indexPathForItemAtPoint:point];
}
- (CGFloat)offsetForFirstVisibleItemWithScrollDirection:(UICollectionViewScrollDirection)scrollDirection {
NSIndexPath *const indexPath = [self indexPathForFirstVisibleItem];
if (indexPath) {
const OffsetRange offset = [self _offsetRangeForIndexPath:indexPath supplementaryKinds:nil scrollDirection:scrollDirection];
switch (scrollDirection) {
case UICollectionViewScrollDirectionHorizontal:
return self.collectionView.contentInset.left + self.collectionView.contentOffset.x - offset.min;
case UICollectionViewScrollDirectionVertical:
return self.collectionView.contentInset.top + self.collectionView.contentOffset.y - offset.min;
}
} else {
return 0;
}
}
- (OffsetRange)_offsetRangeForIndexPath:(NSIndexPath *)indexPath
supplementaryKinds:(nullable NSArray<NSString *> *)supplementaryKinds
scrollDirection:(UICollectionViewScrollDirection)scrollDirection {
const NSUInteger section = indexPath.section;
// collect the layout attributes for the cell and supplementary views for the first index
// this will break if there are supplementary views beyond item 0
NSMutableArray<UICollectionViewLayoutAttributes *> *attributes = nil;
const NSInteger numberOfItems = [collectionView numberOfItemsInSection:section];
const NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
if (numberOfItems > 0) {
attributes = [self _layoutAttributesForItemAndSupplementaryViewAtIndexPath:indexPathFirstElement
attributes = [self _layoutAttributesForItemAndSupplementaryViewAtIndexPath:indexPath
supplementaryKinds:supplementaryKinds].mutableCopy;
if (numberOfItems > 1) {
@ -241,7 +341,7 @@
} else {
NSMutableArray *supplementaryAttributes = [NSMutableArray new];
for (NSString* supplementaryKind in supplementaryKinds) {
UICollectionViewLayoutAttributes *supplementaryAttribute = [self _layoutAttributesForSupplementaryViewOfKind:supplementaryKind atIndexPath:indexPathFirstElement];
UICollectionViewLayoutAttributes *supplementaryAttribute = [self _layoutAttributesForSupplementaryViewOfKind:supplementaryKind atIndexPath:indexPath];
if (supplementaryAttribute != nil) {
[supplementaryAttributes addObject: supplementaryAttribute];
}
@ -249,8 +349,7 @@
attributes = supplementaryAttributes;
}
CGFloat offsetMin = 0.0;
CGFloat offsetMax = 0.0;
OffsetRange offset = (OffsetRange) { .min = 0, .max = 0 };
for (UICollectionViewLayoutAttributes *attribute in attributes) {
const CGRect frame = attribute.frame;
CGFloat originMin;
@ -267,77 +366,16 @@
}
// find the minimum origin value of all the layout attributes
if (attribute == attributes.firstObject || originMin < offsetMin) {
offsetMin = originMin;
if (attribute == attributes.firstObject || originMin < offset.min) {
offset.min = originMin;
}
// find the maximum end value of all the layout attributes
if (attribute == attributes.firstObject || endMax > offsetMax) {
offsetMax = endMax;
if (attribute == attributes.firstObject || endMax > offset.max) {
offset.max = endMax;
}
}
const CGFloat offsetMid = (offsetMin + offsetMax) / 2.0;
const CGFloat collectionViewWidth = collectionView.bounds.size.width;
const CGFloat collectionViewHeight = collectionView.bounds.size.height;
const UIEdgeInsets contentInset = collectionView.ig_contentInset;
CGPoint contentOffset = collectionView.contentOffset;
switch (scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
switch (scrollPosition) {
case UICollectionViewScrollPositionRight:
contentOffset.x = offsetMax - collectionViewWidth + contentInset.right;
break;
case UICollectionViewScrollPositionCenteredHorizontally: {
const CGFloat insets = (contentInset.left - contentInset.right) / 2.0;
contentOffset.x = offsetMid - collectionViewWidth / 2.0 - insets;
break;
}
case UICollectionViewScrollPositionLeft:
case UICollectionViewScrollPositionNone:
case UICollectionViewScrollPositionTop:
case UICollectionViewScrollPositionBottom:
case UICollectionViewScrollPositionCenteredVertically:
contentOffset.x = offsetMin - contentInset.left;
break;
}
const CGFloat maxOffsetX = collectionView.contentSize.width - collectionView.frame.size.width + contentInset.right;
const CGFloat minOffsetX = -contentInset.left;
contentOffset.x += additionalOffset;
contentOffset.x = MIN(contentOffset.x, maxOffsetX);
contentOffset.x = MAX(contentOffset.x, minOffsetX);
break;
}
case UICollectionViewScrollDirectionVertical: {
switch (scrollPosition) {
case UICollectionViewScrollPositionBottom:
contentOffset.y = offsetMax - collectionViewHeight + contentInset.bottom;
break;
case UICollectionViewScrollPositionCenteredVertically: {
const CGFloat insets = (contentInset.top - contentInset.bottom) / 2.0;
contentOffset.y = offsetMid - collectionViewHeight / 2.0 - insets;
break;
}
case UICollectionViewScrollPositionTop:
case UICollectionViewScrollPositionNone:
case UICollectionViewScrollPositionLeft:
case UICollectionViewScrollPositionRight:
case UICollectionViewScrollPositionCenteredHorizontally:
contentOffset.y = offsetMin - contentInset.top;
break;
}
// 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 += additionalOffset;
contentOffset.y = MIN(contentOffset.y, maxOffsetY);
contentOffset.y = MAX(contentOffset.y, minOffsetY);
break;
}
}
[collectionView setContentOffset:contentOffset animated:animated];
return offset;
}
#pragma mark - Editing

View file

@ -928,6 +928,241 @@
XCTAssertTrue(lastSection.wasDisplayed);
}
- (void)test_whenScrolledVertically_thatIndexPathForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 3);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(0, 8) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(0, 12) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:1]);
[self.collectionView setContentOffset:CGPointMake(0, 35) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(0, 40) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:1 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(0, 59) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:2 inSection:2]);
}
- (void)test_whenScrolledVerticallyWithContentInset_thatIndexPathForFirstVisibleItemIsCorrect {
const CGFloat inset = 5;
self.collectionView.contentInset = UIEdgeInsetsMake(inset, 0, inset, 0);
self.dataSource.objects = @[@1, @2, @3];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 3);
[self.collectionView setContentOffset:CGPointMake(0, 0 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(0, 8 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(0, 12 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:1]);
[self.collectionView setContentOffset:CGPointMake(0, 35 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(0, 40 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:1 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(0, 59 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:2 inSection:2]);
}
- (void)test_whenScrolledHorizontallyWithSingleRow_thatIndexPathForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3, @4];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.frame = CGRectMake(0, 0, 300, 10);
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 4);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(80, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(120, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:1]);
[self.collectionView setContentOffset:CGPointMake(350, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(400, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:1 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(590, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:2 inSection:2]);
}
- (void)test_whenScrolledHorizontallyWithSingleRowWithContentInset_thatIndexPathForFirstVisibleItemIsCorrect {
const CGFloat inset = 172;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);
self.dataSource.objects = @[@1, @2, @3, @4];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.frame = CGRectMake(0, 0, 300, 10);
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 4);
[self.collectionView setContentOffset:CGPointMake(0 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(80 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(120 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:1]);
[self.collectionView setContentOffset:CGPointMake(350 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(400 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:1 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(590 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:2 inSection:2]);
}
- (void)test_whenScrolledHorizontallyWithStackedSections_thatIndexPathForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(80, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(120, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:1]);
[self.collectionView setContentOffset:CGPointMake(350, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:3]);
[self.collectionView setContentOffset:CGPointMake(400, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:4]);
[self.collectionView setContentOffset:CGPointMake(590, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:5]);
}
- (void)test_whenScrolledVertically_thatOffsetForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.collectionView setContentOffset:CGPointMake(0, 8) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 8);
[self.collectionView setContentOffset:CGPointMake(0, 12) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 2);
[self.collectionView setContentOffset:CGPointMake(0, 35) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 5);
[self.collectionView setContentOffset:CGPointMake(0, 40) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.collectionView setContentOffset:CGPointMake(0, 59) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 9);
}
- (void)test_whenScrolledVerticallyWithContentInset_thatOffsetForFirstVisibleItemIsCorrect {
const CGFloat inset = 5;
self.collectionView.contentInset = UIEdgeInsetsMake(inset, 0, inset, 0);
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.collectionView setContentOffset:CGPointMake(0, 0 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.collectionView setContentOffset:CGPointMake(0, 8 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 8);
[self.collectionView setContentOffset:CGPointMake(0, 12 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 2);
[self.collectionView setContentOffset:CGPointMake(0, 35 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 5);
[self.collectionView setContentOffset:CGPointMake(0, 40 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.collectionView setContentOffset:CGPointMake(0, 59 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 9);
}
- (void)test_whenScrolledHorizontallyWithSingleRow_thatOffsetForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3, @4];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.frame = CGRectMake(0, 0, 300, 10);
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 4);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(80, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 80);
[self.collectionView setContentOffset:CGPointMake(120, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 20);
[self.collectionView setContentOffset:CGPointMake(350, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 50);
[self.collectionView setContentOffset:CGPointMake(400, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(599, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 99);
}
- (void)test_whenScrolledHorizontallyWithSingleRowWithContentInset_thatOffsetForFirstVisibleItemIsCorrect {
const CGFloat inset = 172;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);
self.dataSource.objects = @[@1, @2, @3, @4];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.frame = CGRectMake(0, 0, 300, 10);
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 4);
[self.collectionView setContentOffset:CGPointMake(0 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(80 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 80);
[self.collectionView setContentOffset:CGPointMake(120 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 20);
[self.collectionView setContentOffset:CGPointMake(350 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 50);
[self.collectionView setContentOffset:CGPointMake(400 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(599 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 99);
}
- (void)test_whenScrolledHorizontallyWithStackedSections_thatOffsetForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(80, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 80);
[self.collectionView setContentOffset:CGPointMake(120, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 20);
[self.collectionView setContentOffset:CGPointMake(350, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 50);
[self.collectionView setContentOffset:CGPointMake(400, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(599, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 99);
}
- (void)test_whenScrollToIndexPathWithAdditionalOffsetVerticially_thatOffsetForFirstVisibleItemIsCorrect {
const CGFloat inset = 5;
self.collectionView.contentInset = UIEdgeInsetsMake(inset, 0, inset, 0);
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:7 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 7);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:3 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 3);
[self.adapter scrollToObject:@4 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:9 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 9);
}
- (void)test_whenScrollToIndexPathWithAdditionalOffsetHorizontally_thatOffsetForFirstVisibleItemIsCorrect {
const CGFloat inset = 172;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:73 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 73);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:36 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 36);
[self.adapter scrollToObject:@4 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:99 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 99);
}
- (void)test_whenQueryingIndexPath_withOOBSectionController_thatNilReturned {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];