Friday, June 11, 2010

UIScrollView looping and lazy loading

Recently, I got to do some fun exploration of what you can do with a UIScrollView. Specifically, in the areas of Lazy Loading views/tiles, and looping around the scrollview when the user scrolls to the beginning or end. For this usage, the scroll 'view' is a full-screen window into the scrolling canvas which displays one of the contained views, one at a time, with the bounce-style scrolling behavior as you scroll left and right through the pages/views in the canvas.

Some notes follow:

Lazy Loading a UIScrollView

To lazy load views inside a UIScrollView, load greedily around the currently visible view by hooking into the

- (void)scrollViewDidScroll:(UIScrollView *)sender


method of UIScrollViewDelegate

Assuming a uniform page size in your UIScrollView, you can detect which page is being scrolled 'toward' with some math like this:

BOOL isScrollingRight = _scrollView.contentOffset.x > _previousContentOffsetX;

_mostRecentScrollWasRight = isScrollingRight;

_previousContentOffsetX = _scrollView.contentOffset.x;


CGFloat pageWidth = _scrollView.frame.size.width;


int scrollingToPageNum = isScrollingRight ? (ceil((_scrollView.contentOffset.x - pageWidth) / pageWidth) + 1) : (floor((_scrollView.contentOffset.x -pageWidth) / pageWidth) + 1);


And you can even calculate the percent of that page that is on-screen (assuming one page on-screen at a time) with

int percentOfScrollToPageOnscreen = isScrollingRight ? floor((((int)_scrollView.contentOffset.x % (int)pageWidth) / pageWidth)*100)

: floor((1 - ((int)_scrollView.contentOffset.x % (int)pageWidth) / pageWidth) * 100);


Use that percent as a gauge to know when to lazy load the next set of pages. I used 3% for the app I writing at the moment, and scrolling is fluid. It's important to note that you'll want to load the page you're scrolling to, and the pages around it, to avoid a visual flickering.

For a pattern of how to lazy load controllers themselves, see the PageControl example provided by Apple.

That's the bulk of the complexity for lazy loading views in a UIScrollView.

Looping a UIScrollView

To add the ability to loop around from the last item in your UIScrollView, to the first item, and vice versa, the simplest technique is to dupe your last page at the beginning, dupe your first page at the end, and then hook into

- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView


to detect the landing page like so

int landingPage = floor((self._scrollView.contentOffset.x - self._scrollView.frame.size.width / _pageCount) / self._scrollView.frame.size.width) + 1;


and scroll them 'around' to the other side (beginning when at the end, end when at the beginning) without any animation/visual clue:

if (landingPage == 0 ) {

[_scrollView scrollRectToVisible:CGRectMake(_pageSize.size.width*(_pageCount-2),0,_pageSize.size.width,_pageSize.size.height) animated:NO];

} else if (landingPage == (_pageCount-1)) {

[_scrollView scrollRectToVisible:CGRectMake(_pageSize.size.width*1,0,_pageSize.size.width,_pageSize.size.height) animated:NO];

}


Code for this entry is available in a gist: http://gist.github.com/434586