0

I've checked through few solutions to the problem of infinite scrolling in UICollectionView, but none of them works perfectly (1, 2, 3 and even tutorial - 4). Main problem is with the fact, that UICollectionView contains images. When i scroll to first or last item (to make it infinite), before they load i can see a very short 'blink' that has to be removed. I don't know how to fix it, any help will be kindly welcomed.

I should mention, that the gallery i'm making shouldn't allow for any user interaction, it should scroll by itself (a presentation lets say).

Two most interesting approaches I've tried:

Method 1: 14 items in collectionView, in such order: @[8, 9, @[0-9], 0, 1] (so 8,9,1,2 are duplicated)

// a timer to move everything
_timer = [NSTimer scheduledTimerWithTimeInterval:0.02f target:self selector:@selector(scroll) userInfo:nil repeats:YES];
// timer selector
- (void)scroll {
    self.collectionView.contentOffset = CGPointMake(self.collectionView.contentOffset.x+1, self.collectionView.contentOffset.y);
}

// check if we need to change offset
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"something" forIndexPath:indexPath];
    if ([self scrollProperly]) {
        //        return cell;
    }
}

// method to check & set proper offset (author: D33pN16h7)
- (BOOL)scrollProperly {
    // check if near the end or beginning
    BOOL test = NO;
    CGFloat currentOffsetX = self.collectionView.contentOffset.x;
    CGFloat currentOffSetY = self.collectionView.contentOffset.y;
    CGFloat contentWidth = self.collectionView.contentSize.width;

    if (currentOffsetX < (contentWidth / 6.0f)) {
        self.collectionView.contentOffset = CGPointMake((currentOffsetX + (contentWidth/2)), currentOffSetY);
        test = YES;
    }
    if (currentOffsetX > ((contentWidth * 4)/ 6.0f)) {
        self.collectionView.contentOffset = CGPointMake((currentOffsetX - (contentWidth/2)), currentOffSetY);
        test = YES;
    }
    return test;
}

Second approach: 20 items in collectionView, in such order: @[@[0-9], @[0-9]] (so whole array is duplicated). There is also timer, same is in previous method. Instead of running [self scrollProperly] in collectionView:cellForItemAtIndexPath: here we use:

if (indexPath.row == 2) {
    [collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:([collectionView numberOfItemsInSection:indexPath.section] - 3) inSection:indexPath.section] atScrollPosition:UICollectionViewScrollPositionNone animated:NO];
} else if (indexPath.row == ([collectionView numberOfItemsInSection:indexPath.section] - 1)) {
    [collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:indexPath.section] atScrollPosition:UICollectionViewScrollPositionNone animated:NO];
}
Community
  • 1
  • 1
Nat
  • 9,591
  • 5
  • 52
  • 99
  • I've also watched the Apple 'Advanced ScrollView Techniques' but this doesn't help as it changes the frames and in `UICollectionView` one can't mess with cell frames. – Nat Nov 25 '13 at 10:34

2 Answers2

1

Ok, my final solution was to make the scroll really long - in collectionView:cellForItemAtIndexPath: I'm reading from my database modulo its size. In collectionView:numberOfItemsInSection: there is a constant size. When i reach the constant size i scroll to the first row. Because I've made constant size a large number flashing isn't irritating to user and the scroll is in reality infinite. The solution isn't perfect but I don't have any other idea how to fix it.

Nat
  • 9,591
  • 5
  • 52
  • 99
1

I know this is a little late but I figured out how to do implement an infinitely scrollable UICollectionView;

Depending on how your cells are laid out, this part may differ but essentially it breaks down into these steps.

1) Modify your datasource and duplicate as many cells are you need on the front and back of your data array.

For example, if your datasource array looks like this:

NSArray* array = @[@1, @2, @3, @4, @5];

and let's say that they each take up 1/5 of the view height, you would want to modify your array to look like this:

NSArray* array = @[@4, @5, @1, @2, @3, @4, @5, @1, @2, @3];

This way, your collectionview is essentially 'repeating' those cells to give the illusion that it keeps going.

This part is the hardest because how you modify your data array is dependent on how many cells fit on screen at once. If only one cell fits on screen at a time and your data array was @[@1, @2, @3], that could easily be modified to @[@3, @1, @2, @3, @1]; (That's the example from this awesome tutorial.

So why do we do this? It's because the collectionview needs something to scroll to, i.e. extra padding on the front and back of the collection view.

2) Using Key Value Observing to observe the contentOffset of the collectionView.

[self.collectionView addObserver:self forKeyPath:@"contentOffset" options:0 context:NULL];

3) Check if the contentOffset has hit an edge.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    CGFloat offset = self.collectionView.contentOffset.y;
    if(offset <= 1){
        [self.collectionView setContentOffset:CGPointMake(0, self.collectionView.height-2)];
        [self.collectionView reloadData];
}else if(offset >= self.collectionView.height-1){
        [self.collectionView setContentOffset:CGPointMake(0, 2)];
        [self.collectionView reloadData]; //call to reload the now visible cells, ensure delegate gets called
    }
}

If the contentOffset is within a few pixels of the top edge, then set the contentOffset so that the collection view silently scrolls down the page without the user knowing. Do the same when the contentOffset reaches the other edge.

Two VERY IMPORTANT things.

1) When you first initialize your collectionView, set the contentOffset to be something like (0, 3). This way, it WONT trigger the if statement inside the KVO method. 2) When setting the contentOffset in the KVO method, also make sure that the point you're setting won't trigger the if statement when the method gets called.

Remember, since you're observing for contentOffset, setting contentOffset will trigger that method immediately! As long as it doesn't set contentOffset the 2nd time through thus causing an infinite loop of change notifications, you should be fine.

Also don't forget to set collectionView.bounces to YES.

barndog
  • 6,314
  • 8
  • 45
  • 90
  • I think changing the content offset will also cause this blinking. Dequeue cells will trigger reloading and thus the image will blink for a very short period. Still your answer is interesting to read as i didn't try this approach :) – Nat Feb 11 '14 at 14:15
  • I would recommend, as long as your table is short, to load every index. There are certainly some loading issues with this approach, even a few I haven't figured out yet. – barndog Feb 11 '14 at 19:11