UICollectionView Pagination  

I was inspired by @jaredsinclair a while ago, because I had noticed, as he had, the lovely side-swiping UI in the Twitter iOS app. It is paginated, but there is some of each neighboring card visible. So it has three key features: the first and last cards align to their edge; the remaining cards all align to the center; and you can only swipe one card at a time. He posted a rough-and-dirty method.

I had been wanting to try this UI out for something, so I looked to his implementation, as well as another that I came across. They both required some knowledge of external state. First of all, I just wanted to understand how to implement it, then as I did so, I found myself trying to remove as much state as possible. I basically got rid of all the state. I have not taken the next step to turn it into a category method, but maybe later. The formatting below, kinda stinks, so here is the gist, and here is a dropbox link for a video

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    targetContentOffset->x = [self  targetOffsetX:targetContentOffset->x inCollectionView:(UICollectionView *)scrollView velocity:velocity];
}

- (CGFloat)targetOffsetX:(CGFloat)offsetX inCollectionView:(UICollectionView *)collectionView velocity:(CGPoint)velocity {
    if (![collectionView isKindOfClass:[UICollectionView class]]) {
        return offsetX;
    }
    NSInteger index = [self indexForScrollView:collectionView targetOffsetX:offsetX velocity:velocity];
    UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];
    return (index == 0) ? 0 : cell.frame.origin.x - (collectionView.frame.size.width - cell.frame.size.width)/2 ;
}

#define kDragVelocityDampener .85

- (NSInteger)indexForScrollView:(UICollectionView *)collectionView targetOffsetX:(CGFloat)offsetX velocity:(CGPoint)velocity {
    NSInteger returnIndex = [self indexOfOffset:offsetX collectionView:collectionView velocity:velocity];
    CGFloat originOfPan =[collectionView.panGestureRecognizer translationInView:self.view].x + collectionView.contentOffset.x;
    NSInteger origIndex = [self indexOfOffset:originOfPan collectionView:collectionView velocity:velocity];
    //Note: as the width of the cell gets much smaller than the width of the collection view, this becomes jerky.
    if (labs(returnIndex - origIndex)> 1) {
        return origIndex + ((velocity.x > 0) ? 1 : - 1);
    }
    return  returnIndex;
}

- (NSInteger)indexOfOffset:(CGFloat)xOffset collectionView:(UICollectionView *)collectionView velocity:(CGPoint)velocity {
    if (xOffset == 0) {
        return 0;
    } else {
        CGFloat changeX = fabs(xOffset - collectionView.contentOffset.x)*kDragVelocityDampener;;
        CGFloat width = [[collectionView visibleCells].firstObject frame].size.width;
        return (velocity.x >= 0.f) ? ceil((xOffset - changeX)/width) : floor((xOffset + changeX)/width);
    }
}
 
2
Kudos
 
2
Kudos

Now read this

Swift Operators – Custom Operator Wishes

As I am reading more about swift and what people are doing with it, I do love seeing some of the ways that it can bring conciseness, particularly with custom operators. Two recent examples that inspired this post are the Runes functional... Continue →