4

I have a UICollectionView with 6 pages, and paging enabled, and a UIPageControl. What I want is, when I came to the last page, if I drag to right, UICollectionView reloads from first page seamlessly.

- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{

// The key is repositioning without animation
if (collectionView.contentOffset.x == 0) {
    // user is scrolling to the left from image 1 to image 10.
    // reposition offset to show image 10 that is on the right in the scroll view
    [collectionView scrollRectToVisible:CGRectMake(collectionView.frame.size.width*(pageControl.currentPage-1),0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
else if (collectionView.contentOffset.x == 1600) {
    // user is scrolling to the right from image 10 to image 1.
    // reposition offset to show image 1 that is on the left in the scroll view
    [collectionView scrollRectToVisible:CGRectMake(0,0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];

}
pageControlUsed = NO;

}

It doesn't work like I want. What can I do?

Burak
  • 5,406
  • 17
  • 64
  • 105
  • 2
    Be careful when using scrollViewDidEndDecellerating - if a user scrolls too fast the method won't get called between every page. – lehn0058 Dec 10 '12 at 03:34

3 Answers3

2

Here's what I ended up with for my UICollectionView (horizontal scrolling like the UIPickerView):

@implementation UIInfiniteCollectionView

- (void) recenterIfNecessary {
    CGPoint currentOffset = [self contentOffset];
    CGFloat contentWidth = [self contentSize].width;
    // don't just snap to center, since this might be done in the middle of a drag and not aligned.  Make sure we account for that offset
    CGFloat offset = kCenterOffset - currentOffset.x;
    int delta = -round(offset / kCellSize);
    CGFloat shift = (offset + delta * kCellSize);
    offset += shift;
    CGFloat distanceFromCenter = fabs(offset);

    // don't always recenter, just if we get too far from the center.  Eliza recommends a quarter of the content width
    if (distanceFromCenter > (contentWidth / 4.0)) {
        self.contentOffset = CGPointMake(kCenterOffset, currentOffset.y);
        // move subviews back to make it appear to stay still
        for (UIView *subview in self.subviews) {
            CGPoint center = subview.center;
            center.x += offset;
            subview.center = center;
        }
        // add the offset to the index (unless offset is 0, in which case we'll assume this is the first launch and not a mid-scroll)
        if (currentOffset.x > 0) {
            int delta = -round(offset / kCellSize);
            // MODEL UPDATE GOES HERE
        }
    }
}

- (void) layoutSubviews { // called at every frame of scrolling
    [super layoutSubviews];
    [self recenterIfNecessary];
}

@end

Hope this helps someone.

Gujamin
  • 4,056
  • 2
  • 22
  • 31
  • What is the constant kCenterOffset? – nonamelive May 21 '13 at 06:54
  • I think I just picked some arbitrarily large number so that it wouldn't ever be reached but not so big that it would have to render too much. This is what it's defined as in my code: `#define kCenterOffset 2515` – Gujamin May 21 '13 at 13:53
1

I have not used UICollectionView for infinite scrolling, but when doing it with a UIScrollView you first adjust your content offset (instead of using scrollRectToVisible) to the location you want. Then, you loop through each subview in your scroller and adjust their coordinates either to the right or left based on the direction the user was scrolling. Finally, if either end is beyond the bounds you want them to be, move them to the far other end. Their is a very good WWDC video from apple about how to do infinite scrolling you can find here: http://developer.apple.com/videos/wwdc/2012/

lehn0058
  • 19,077
  • 14
  • 65
  • 107
  • 1
    Does this combine well with a UITableView or UICollectionView (both subclasses of UIScrollView)? Can you provide an example in these cases since we do not directly layout the subviews? – Gujamin Jan 10 '13 at 16:03
  • It does combine well. In the example provided by Apple, you need to subclass UIscrollView. Since both UITableView and UICollectionView already do this, you just need to extend one of those classes and implement similar methods. If you don't directly layout the cells in the UICollectionView then when your collection view pages, you will need to call reloadData on your collection view and adjust which cells are returned at each NSIndexPath – lehn0058 Jan 11 '13 at 01:30
  • and the excellent one from WWDC 2011 https://developer.apple.com/videos/play/wwdc2011/104/ – iGranDav Dec 07 '17 at 14:15
1

I've been using the Street Scroller sample to create an infinite scroller for images. That works fine until I wanted to set pagingEnabled = YES; Tried tweaking around the recenterIfNecessary code and finally realized that it's the contentOffset.x that has to match the frame of the subview that i want visible when paging stops. This really isn't going to work in recenterIfNecessary since you have no way of knowing it will get called from layoutSubviews. If you do get it adjusted right, the subview may pop out from under your finger. I do the adjustment in scrollViewDidEndDecelerating. So far I haven't had problems with scrolling fast. It will work and simulate paging even when pagingEnabled is NO, but it looks more natural with YES.

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [super scrollViewDidEndDecelerating:scrollView];

    CGPoint currentOffset = [self contentOffset];

    // find the subview that is the closest to the currentOffset.
    CGFloat closestOriginX = 999999;
    UIView *closestView = nil;
    for (UIView *v in self.visibleImageViews) {
        CGPoint origin = [self.imageContainerView convertPoint:v.frame.origin toView:self];
        CGFloat distanceToCurrentOffset = fabs(currentOffset.x - origin.x);
        if (distanceToCurrentOffset <= closestOriginX) {
            closestView = v;
            closestOriginX = distanceToCurrentOffset;
        }
    }

    // found the closest view, now find the correct offset
    CGPoint origin = [self.imageContainerView convertPoint:closestView.frame.origin toView:self];
    CGPoint center = [self.imageContainerView convertPoint:closestView.center toView:self];
    CGFloat offsetX = currentOffset.x - origin.x;

    // adjust the centers of the subviews
    [UIView animateWithDuration:0.1 animations:^{
        for (UIView *v in self.visibleImageViews) {
            v.center = [self convertPoint:CGPointMake(v.center.x+offsetX, center.y) toView:self.imageContainerView];
        }
    }];
}
Jayesh Thanki
  • 1,879
  • 2
  • 21
  • 29
Dan Loughney
  • 4,229
  • 3
  • 22
  • 37