UIScrollView Infinite Scrolling

Viewed 46842

I'm attempting to setup a scrollview with infinite (horizontal) scrolling.

Scrolling forward is easy - I have implemented scrollViewDidScroll, and when the contentOffset gets near the end I make the scrollview contentsize bigger and add more data into the space (i'll have to deal with the crippling effect this will have later!)

My problem is scrolling back - the plan is to see when I get near the beginning of the scroll view, then when I do make the contentsize bigger, move the existing content along, add the new data to the beginning and then - importantly adjust the contentOffset so the data under the view port stays the same.

This works perfectly if I scroll slowly (or enable paging) but if I go fast (not even very fast!) it goes mad! Heres the code:

- (void) scrollViewDidScroll:(UIScrollView *)scrollView {

    float pageNumber = scrollView.contentOffset.x / 320;
    float pageCount = scrollView.contentSize.width / 320;

    if (pageNumber > pageCount-4) {
        //Add 10 new pages to end
        mainScrollView.contentSize = CGSizeMake(mainScrollView.contentSize.width + 3200, mainScrollView.contentSize.height);
        //add new data here at (320*pageCount, 0);
    }

    //*** the problem is here - I use updatingScrollingContent to make sure its only called once (for accurate testing!)
    if (pageNumber < 4 && !updatingScrollingContent) {

        updatingScrollingContent = YES;

        mainScrollView.contentSize = CGSizeMake(mainScrollView.contentSize.width + 3200, mainScrollView.contentSize.height);
        mainScrollView.contentOffset = CGPointMake(mainScrollView.contentOffset.x + 3200, 0);
        for (UIView *view in [mainContainerView subviews]) {
            view.frame = CGRectMake(view.frame.origin.x+3200, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
        }
        //add new data here at (0, 0);      
    }

    //** MY CHECK!
    NSLog(@"%f", mainScrollView.contentOffset.x);
}

As the scrolling happens the log reads: 1286.500000 1285.500000 1284.500000 1283.500000 1282.500000 1281.500000 1280.500000

Then, when pageNumber<4 (we're getting near the beginning): 4479.500000 4479.500000

Great! - but the numbers should continue to go down in the 4,000s but the next log entries read: 1278.000000 1277.000000 1276.500000 1275.500000 etc....

Continiuing from where it left off!

Just for the record, if scrolled slowly the log reads: 1294.500000 1290.000000 1284.500000 1280.500000 4476.000000 4476.000000 4473.000000 4470.000000 4467.500000 4464.000000 4460.500000 4457.500000 etc....

Any ideas????

Thanks

Ben.

10 Answers

So one issue is that setting the contentOffset while in the scrollViewDidScroll: delegate method will cause the delegate method to fire again while you are inside it. So what I do is remove the delegate > setContentOffset > set the delegate again.

Here's my code:

#import "ViewController.h"

@interface ViewController () <UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (strong, nonatomic) NSMutableArray *labels;
@property (weak, nonatomic) UILabel *originLabel;

- (void)addScrollViewLabels;
- (void)adjustOrigins:(float)deltaX;

@end


@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // just some starting size
    CGSize size = self.scrollView.frame.size;
    CGSize contentSize = CGSizeMake(size.width * 4, size.height);
    [self.scrollView setContentSize:contentSize];

    // just some starting offset
    CGPoint contentOffset = CGPointMake((contentSize.width / 2), 0);
    [self.scrollView setContentOffset:contentOffset];

    [self addScrollViewLabels];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGSize contentSize = scrollView.contentSize;
    CGSize size = scrollView.frame.size;
    CGPoint contentOffset = scrollView.contentOffset;

    const float kContentOffsetBuffer = 100;
    const float kContentSizeGrowth = (4 * size.width);

    BOOL shouldGrowContentSize = (contentOffset.x > (contentSize.width - size.width - kContentOffsetBuffer))
                                || (contentOffset.x < (kContentOffsetBuffer));
    if (shouldGrowContentSize) {
        // the contentOffset has reached a point where we need to grow the contentSize
        CGSize adjustedContentSize = CGSizeMake(contentSize.width + kContentSizeGrowth, contentSize.height);
        [self.scrollView setContentSize:adjustedContentSize];

        if(contentOffset.x < (kContentOffsetBuffer)) {
            // the growth needs to happen on the left side which means that we need to adjust the contentOffset to compensate for the growth.
            // this is not necessary when growth happens to the right since the contentOffset is the same.
            CGPoint adjustedContentOffset = CGPointMake(contentOffset.x + kContentSizeGrowth, contentOffset.y);
            [self.scrollView setDelegate:nil];
            [self.scrollView setContentOffset:adjustedContentOffset];
            [self.scrollView setDelegate:self];
            [self adjustOrigins:kContentSizeGrowth];
        }

        [self addScrollViewLabels];
    }
}


- (void)addScrollViewLabels {
    const float kOriginY = 100;

    if (!self.labels) {
        self.labels = [NSMutableArray array];
        float originX = [self.scrollView contentOffset].x;
        UILabel *label0 = [[UILabel alloc] initWithFrame:CGRectMake(originX, kOriginY, 100, 30)];
        label0.text = @"0";
        [self.scrollView addSubview:label0];
        [self.labels addObject:label0];
        self.originLabel = label0;
    }

    CGSize contentSize = [self.scrollView contentSize];
    const float kIncrementAmount = 75;
    NSInteger indexOfOriginLabel = [self.labels indexOfObject:self.originLabel];

    // add labels to the right
    UILabel *lastLabel = [self.labels lastObject];
    float lastOriginX = lastLabel.frame.origin.x;
    for (float x = (lastOriginX + kIncrementAmount); (x < (contentSize.width)) ; x += kIncrementAmount) {
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x, kOriginY, 100, 30)];
        NSInteger indexFromOrigin = ([self.labels count] - indexOfOriginLabel);
        label.text = [@(indexFromOrigin) description];
        [self.scrollView addSubview:label];
        [self.labels addObject:label];
        [label setNeedsDisplay];
    }

    // add labels to the left
    UILabel *firstLabel = [self.labels firstObject];
    float firstOriginX = firstLabel.frame.origin.x;
    for (float x = (firstOriginX - kIncrementAmount); (x >= 0) ; x -= kIncrementAmount) {
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x, kOriginY, 100, 30)];
        NSInteger indexFromOrigin = -(indexOfOriginLabel + 1);
        label.text = [@(indexFromOrigin) description];
        [self.scrollView addSubview:label];
        [self.labels insertObject:label atIndex:0];
        indexOfOriginLabel++;
        [label setNeedsDisplay];
    }
}

- (void)adjustOrigins:(float)deltaX {
    for (UILabel *label in self.labels) {
        CGRect frame = label.frame;
        frame.origin.x += deltaX;
        [label setFrame:frame];
        [label setNeedsDisplay];
    }
}


@end
Related