Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIScrollView Infinite Scrolling

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.

like image 496
Ben Robinson Avatar asked Aug 07 '10 11:08

Ben Robinson


People also ask

How do I turn on infinite scroll?

However, you can turn it on or off any time by toggling the “Infinite Scroll” option on My Site → Settings → Writing. Additionally, adding a footer widget will also disable the Infinite Scroll option.

Does infinite scroll improve performance?

Above all else, infinite scroll is designed to boost user engagement and keep viewers on the page for as long as possible. If visitors have no particular goal in mind, infinite scrolling will continue to roll out relevant content in a way that is efficient, digestible, and interruption-free.

What is endless scrolling called?

What Is Infinite Scroll? A web design technique where, as the user scrolls down a page, more content automatically and continuously loads at the bottom, eliminating the user's need to click to the next page. The idea behind infinite scroll is that it allows people to enjoy a frictionless browsing experience.

Should you use infinite scroll?

Infinite scrolling is best for a “browsing” mindset They do this for two key reasons: it boosts user engagement and is better for mobile devices. Users don't have to click buttons to load up the next batch of results: they can scroll, which is an easy action for smartphones.


3 Answers

I found a really great example app by Apple to implement Infinite Scrolling using a similar idea in the OP. Very simple and most importantly no tearing.

http://developer.apple.com/library/ios/#samplecode/StreetScroller/Introduction/Intro.html

They implemented the "content recentering" every time layoutSubviews was called on the UIScrollView.

The only adjustment I made was to recycle the "tiling effect" instead of throwing away old tiles and allocing new ones.

like image 104
rwyland Avatar answered Nov 09 '22 22:11

rwyland


It could be that whatever is setting those numbers in there, is not greatly impressed by you setting the contentOffset under its hands. So it just goes on setting what it thinks should be the contentOffset for the next instant - without verifying if the contentOffset has changed in the meantime.

I would subclass UIScrollView and put the magic in the setContentOffset method. In my experience all content-offset changing passes through that method, even the content-offset changing induced by the internal scrolling. Just do [super setContentOffset:..] at some point to pass the message on to the real UIScrollView.

Maybe if you put your shifting action in there it will work better. You could at least detect the 3000-off setting of contentOffset, and fix it before passing the message on. If you would also override the contentOffset method, you could try and see if you can make a virtual infinite content size, and reduce that to real proportions "under the hood".

like image 23
mvds Avatar answered Nov 09 '22 20:11

mvds


When I had faced this problem I had 3 images, which needed to be able to scroll infinitely in either direction. The optimal solution would probably be to load 3 images and modifying the content panel when user moves to rightmost/ leftmost image but I have found an easier solution. (Did some editing on the code, might contain typos)

Instead of 3 images, I have setup a scroll view with 5 images, like:

3 | 1 | 2 | 3 | 1

and calculated the content view accordingly, whereas the content size is fixed. Here is the code:

for ( NSUInteger i = 1; i <= NO_OF_IMAGES_IN_SCROLLVIEW + 2 ; i ++) {

    UIImage *image;
    if ( i  % 3 == 1){

        image = [UIImage imageNamed:[NSString stringWithFormat:@"img1.png"]];
    }

    else if (i % 3 == 2 ) {

        image = [UIImage imageNamed:[NSString stringWithFormat:@"img2.png"]];

    }

    else {

        image = [UIImage imageNamed:[NSString stringWithFormat:@"img3.png"]];
    }

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake((i-1)*_scrollView.frame.size.width, 0, _scrollView.frame.size.width, _scrollView.frame.size.height)];
    imageView.contentMode=UIViewContentModeScaleToFill;
    [imageView setImage:image];
    imageView.tag=i+1;
    [self.scrollView addSubview:imageView];
}

[self.scrollView setContentOffset:CGPointMake(self.scrollView.frame.size.width, 0)];
[scrMain setContentSize:CGSizeMake(self.scrollView.frame.size.width * ( NO_OF_IMAGES_IN_SCROLLVIEW + 2 ), self.scrollView.frame.size.height)];

Now that the images are added, the only thing is to create the illusion of infinite scrolling. To do that I have "teleported" the user into the three main image, whenever he tries to scroll to one of the outer two images. It is important to do that not-animated, so that the user won't be able to feel it:

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

    if (scrollView.contentOffset.x < scrollView.frame.size.width ){

        [scrollView scrollRectToVisible:CGRectMake(scrollView.contentOffset.x + 3 * scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
    }
}

    else if ( scrollView.contentOffset.x > 4 * scrollView.frame.size.width  ){

        [scrollView scrollRectToVisible:CGRectMake(scrollView.contentOffset.x - 3 * scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
    }
}
like image 39
Yunus Nedim Mehel Avatar answered Nov 09 '22 20:11

Yunus Nedim Mehel