Optimized Image Loading in a UIScrollView

Posted by Michael Gaylord on Stack Overflow See other posts from Stack Overflow or by Michael Gaylord
Published on 2009-07-08T13:54:15Z Indexed on 2010/04/08 21:43 UTC
Read the original article Hit count: 935

I have a UIScrollView that has a set of images loaded side-by-side inside it. You can see an example of my app here: http://www.42restaurants.com. My problem comes in with memory usage. I want to lazy load the images as they are about to appear on the screen and unload images that aren't on screen. As you can see in the code I work out at a minimum which image I need to load and then assign the loading portion to an NSOperation and place it on an NSOperationQueue. Everything works great apart from a jerky scrolling experience.

I don't know if anyone has any ideas as to how I can make this even more optimized, so that the loading time of each image is minimized or so that the scrolling is less jerky.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [self manageThumbs];	
}

- (void) manageThumbs{
    int centerIndex = [self centerThumbIndex];
    if(lastCenterIndex == centerIndex){
    	return;
    }

    if(centerIndex >= totalThumbs){
    	return;
    }

    NSRange unloadRange;
    NSRange loadRange;

    int totalChange = lastCenterIndex - centerIndex;
    if(totalChange > 0){ //scrolling backwards
    	loadRange.length = fabsf(totalChange);
    	loadRange.location = centerIndex - 5;
    	unloadRange.length = fabsf(totalChange);
    	unloadRange.location = centerIndex + 6;
    }else if(totalChange < 0){ //scrolling forwards
    	unloadRange.length = fabsf(totalChange);
    	unloadRange.location = centerIndex - 6;
    	loadRange.length = fabsf(totalChange);
    	loadRange.location = centerIndex + 5;
    }
    [self unloadImages:unloadRange];
    [self loadImages:loadRange];
    lastCenterIndex = centerIndex;

    return;
}

- (void) unloadImages:(NSRange)range{
    UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
    for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
    	UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
    	if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
    		ThumbnailView *thumbView = (ThumbnailView *)subview;
    		if(thumbView.loaded){
    			UnloadImageOperation *unloadOperation = [[UnloadImageOperation alloc] initWithOperableImage:thumbView];
    			[queue addOperation:unloadOperation];
    			[unloadOperation release];
    		}
    	}
    }
}

- (void) loadImages:(NSRange)range{
    UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
    for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
    	UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
    	if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
    		ThumbnailView *thumbView = (ThumbnailView *)subview;
    		if(!thumbView.loaded){
    			LoadImageOperation *loadOperation = [[LoadImageOperation alloc] initWithOperableImage:thumbView];
    			[queue addOperation:loadOperation];
    			[loadOperation release];
    		}
    	}
    }
}

EDIT: Thanks for the really great responses. Here is my NSOperation code and ThumbnailView code. I tried a couple of things over the weekend but I only managed to improve performance by suspending the operation queue during scrolling and resuming it when scrolling is finished.

Here are my code snippets:

//In the init method
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:4];


//In the thumbnail view the loadImage and unloadImage methods
- (void) loadImage{
    if(!loaded){
    	NSString *filename = [NSString stringWithFormat:@"%03d-cover-front", recipe.identifier, recipe.identifier]; 
    	NSString *directory = [NSString stringWithFormat:@"RestaurantContent/%03d", recipe.identifier];		

    	NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:@"png" inDirectory:directory];
    	UIImage *image = [UIImage imageWithContentsOfFile:path];

    	imageView = [[ImageView alloc] initWithImage:image andFrame:CGRectMake(0.0f, 0.0f, 176.0f, 262.0f)];
    	[self addSubview:imageView];
    	[self sendSubviewToBack:imageView];
    	[imageView release];
    	loaded = YES;		
    }
}

- (void) unloadImage{
    if(loaded){
    	[imageView removeFromSuperview];
    	imageView = nil;
    	loaded = NO;
    }
}

Then my load and unload operations:

- (id) initWithOperableImage:(id<OperableImage>) anOperableImage{

    self = [super init];
    if (self != nil) {
    	self.image = anOperableImage;
    }
    return self;
}

//This is the main method in the load image operation
- (void)main {
    [image loadImage];
}


//This is the main method in the unload image operation
- (void)main {
    [image unloadImage];
}

© Stack Overflow or respective owner

Related posts about iphone-sdk

Related posts about iphone