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][jaredPost].

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][kegPost]. 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][myGist], and here is a [dropbox link for a video][dropBox]

- (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);
    }
}

[jaredPost]: https://gist.github.com/chrisbrandow/4b35744ac67fc6976033
[kegPost]: http://keighl.com/post/decelerate-uiscrollview-ala-ultravisual/
[kegTwitter]: https://twitter.com/keighl
[myGist]: https://gist.github.com/chrisbrandow/4b35744ac67fc6976033
[dropBox]: https://dl.dropboxusercontent.com/u/6576412/sideSwipe.m4v

 
2
Kudos
 
2
Kudos

Now read this

Swift: map vs. flatMap For Dummies

tl;dr Summary # This is me, figuring out how map and flatMap work in Swift. tl;dr Conclusions: # Think of optionals as an array that can hold 1 or 0 items. Arrays and optionals are both containers that can have map or flatMap applied map... Continue →