When creating paging functionality for iPhone apps, there may be times that an infinite page loop would be desired. For example, if you have a small gallery of photos you are displaying, you may want to swipe through the set and have it start back at the beginning once you reach the end. The user would be able to continue swiping as much as they wanted in one direction to continue to view the content. Here are two strategies for achieving this result:
(UPDATE: Example code below now more extensive and bug free. Example project can be downloaded at end of article.)
Duplicate end caps
The first option is best suited for smaller loops. Suppose you have ten photos to display. When the user is on photo one and swipes left, it will take the user to photo ten. When the user is on photo ten and swipes right, it will take the user to photo one. The logic we will follow here is to add photos in order, but place an duplicate of photo ten to the left of photo one and a duplicate of photo one to the right of photo ten.
Now when the user scrolls to the end, we reposition the content offset of our UIScrollView. By having a duplicate photo at the end and repositioning the offset without using animation, we create a seamless experience for the user.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender { // The key is repositioning without animation if (scrollView.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 [scrollView scrollRectToVisible:CGRectMake(3520,0,320,480) animated:NO]; } else if (scrollView.contentOffset.x == 3840) { // 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 [scrollView scrollRectToVisible:CGRectMake(320,0,320,480) animated:NO]; } }3 Pages Only
There may be times when you want an infinite page loop, but don’t want to load in a lot of content. For example, I recently worked on a project that displayed a different document with an image and text for every day of the year. The client wanted an infinite page loop so that the user would be able to scroll through the documents. However, that would be far too much data to load into a UIScrollView all at once. A different approach was needed.
The logic that I used was to keep the UIScrollView at just three pages. A document would load on each page and the user would always be looking at the document in the middle page. When the user scrolled to a new page, the content for each document would be reset and the offset would go back the user is back to viewing the middle page.
- (void)viewDidLoad { [super viewDidLoad]; documentTitles = [[NSMutableArray alloc] init]; // create our array of documents for (int i = 0; i < 10; i++) { [documentTitles addObject:[NSString stringWithFormat:@"Document %i",i+1]]; } // create placeholders for each of our documents pageOneDoc = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 44)]; pageTwoDoc = [[UILabel alloc] initWithFrame:CGRectMake(320, 0, 320, 44)]; pageThreeDoc = [[UILabel alloc] initWithFrame:CGRectMake(640, 0, 320, 44)]; pageOneDoc.textAlignment = UITextAlignmentCenter; pageTwoDoc.textAlignment = UITextAlignmentCenter; pageThreeDoc.textAlignment = UITextAlignmentCenter; // load all three pages into our scroll view [self loadPageWithId:9 onPage:0]; [self loadPageWithId:0 onPage:1]; [self loadPageWithId:1 onPage:2]; [scrollView addSubview:pageOneDoc]; [scrollView addSubview:pageTwoDoc]; [scrollView addSubview:pageThreeDoc]; // adjust content size for three pages of data and reposition to center page scrollView.contentSize = CGSizeMake(960, 416); [scrollView scrollRectToVisible:CGRectMake(320,0,320,416) animated:NO]; } - (void)loadPageWithId:(int)index onPage:(int)page { // load data for page switch (page) { case 0: pageOneDoc.text = [documentTitles objectAtIndex:index]; break; case 1: pageTwoDoc.text = [documentTitles objectAtIndex:index]; break; case 2: pageThreeDoc.text = [documentTitles objectAtIndex:index]; break; } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)sender { // All data for the documents are stored in an array (documentTitles). // We keep track of the index that we are scrolling to so that we // know what data to load for each page. if(scrollView.contentOffset.x > scrollView.frame.size.width) { // We are moving forward. Load the current doc data on the first page. [self loadPageWithId:currIndex onPage:0]; // Add one to the currentIndex or reset to 0 if we have reached the end. currIndex = (currIndex $gt;= [documentTitles count]-1) ? 0 : currIndex + 1; [self loadPageWithId:currIndex onPage:1]; // Load content on the last page. This is either from the next item in the array // or the first if we have reached the end. nextIndex = (currIndex $gt;= [documentTitles count]-1) ? 0 : currIndex + 1; [self loadPageWithId:nextIndex onPage:2]; } if(scrollView.contentOffset.x $lt; scrollView.frame.size.width) { // We are moving backward. Load the current doc data on the last page. [self loadPageWithId:currIndex onPage:2]; // Subtract one from the currentIndex or go to the end if we have reached the beginning. currIndex = (currIndex == 0) ? [documentTitles count]-1 : currIndex - 1; [self loadPageWithId:currIndex onPage:1]; // Load content on the first page. This is either from the prev item in the array // or the last if we have reached the beginning. prevIndex = (currIndex == 0) ? [documentTitles count]-1 : currIndex - 1; [self loadPageWithId:prevIndex onPage:0]; } // Reset offset back to middle page [scrollView scrollRectToVisible:CGRectMake(320,0,320,416) animated:NO]; }Source Code: http://accella.net/dev/Labs/InfiniteScrollView/InfiniteScrollView.zip
18 Responses
Thank you very much, Jacob!
This helped me a lot!
great! thanks a lot,jacob
Thanks for this Jacob –
is all that’s required before this to create the UIScrollview in the relevant .XIB, or do I need to add any objects (the three pages / UILabels) in that UIScrollView, and doing this in the .m file does the rest?
Any help much appreciated 🙂
James
La Mode Outré – trying to create a photo App for my streetstyle blog and have only managed to create a swipeable NSArray in a UIImageView, but that is a bit cruddy.
great! thank you very much
hi, thnx for such a nice article it helped me a lot to understand how UIScrollView actually works. I further want to enhance the code. currently what im doing I want to nest another scrollview on the parent scroll view at center position. but it is not showing nested scroll view. could you pls help me how to nest scrollview. Regards
If a user swipes to continuously scroll — does/will scrollViewDidEndDecelerating ever get called — if not, what’s the solution?
Combine the continuous scrolling with “loadPageWithId” having a significant cost (ie: read from local storage, > .15 sec to render) and what do you do? Sure, you need to have the load/render off the main thread so there is at least the potential to keep the scroll from stuttering — but then do you see old data or blank (empty/template) views?
Well, that depends on what you mean by ‘it is not showing’. Make sure you frame and contentSize properties are set properly on both parent and nested UIScrollView. Also verify that you are adding both parent and child views to the screen with the appropriate addSubview method.
Hi,
Thanks, this is quiet easy to use and understand… I like it and it helped me a lot.
Thank a lot… Simple in theory but not so simple when you start wasting time in the debugger. Your code works great.
How can we create a scrollview without infinite scroll but using 3 pages only, using this method.
thank you Jacob for sharing what you’ve got. 🙂
Great job, well done!
this works well then the – (void)scrollViewDidEndDecelerating:(UIScrollView *)sender fires, but it does not work if only – (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate fires and decelerate is false. I am trying to have it work like the Kindle app, where if it did not go halfway, then it will snap back to where it was, otherwise it will scroll to the next page.
Good job first of all.
But I see a little problem here.
Since you are using didEnddecelerate delegate method to do your calculations there is a problem.
If user scrolls and before scrolling is ended he scrolls again then didEndDecelerate is not called twice. Hence it would appear as that is the last image. But then user scrolls again and everything is normal.
The problem is while scrolling if user scrolls then didEndDecelerate is not called?
I am struggling with this problem please help anyone!!
Thanks for explaining in simple way, it helped me to understand scrollview and infinite scrolling.
Mhh, i try that for Images (loaded by http requests.)
Have you a idea how can i do that ? I have 1000 images on my server, and i will make a paged uiscollview with three views. So i can loaded in background the next index. But it dosnt work.
Thanks I was looking for the same thing.
Please let me know how to enable autoscroll to this with respect to a timer
Any help appreciated.