Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory problems with displaying large images in UITableView

I have a system which loads alot of large images from the web and displays them in custom table cells. On older devices the memory warnings happen pretty quickly so I implemented a system of deleting some from the table to try to combat this but it didn't work well enough (lots of images were deleted affecting the UI).

So I thought I could load all the images into the device's cache and then load them from there - I've implemented SDWebImage. This is great but I still havent solved the problem of memory allocation as the images are still being displayed all the time and therefore kept in memory - causing crashes.

I think I need to implement a system which shows the images (from the cache) if the cell is being displayed and hide it if the cell is not showing - I'm just stuck at how to build such a system.

Or is this not going to work? Can you really keep the apps memory low (and stop it having memory warnings / crashing) by removing images from its table cells? Or do I just need to carry on with my earlier solution and just delete images/cells until the memory warnings stop?

Updated with code

TableViewController.m

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{   
    if (indexPath.section == 0)
    {
    currentIndexPath = indexPath;

    ImageTableCell *cell = (ImageTableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    ImageDownloader *download = [totalDownloads objectAtIndex:[indexPath row]];

    if (cell == nil) 
    {
        cell = [[[ImageTableCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: CellIdentifier] autorelease];
    }

    cell.imageView.image = download.image;

    return cell;
}
return nil;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{   
    int t = [totalDownloads count];   
    return t;
}

ImageTableCell.m - Custom cell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) 
{
    self.frame = CGRectMake(0.0f, 0.0f, 320.0f, 0.0f);
    self.contentView.frame = CGRectMake(0.0f, 0.0f, 320.0f, 0.0f);

    self.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
    self.contentMode = UIViewContentModeScaleToFill;
    self.autoresizesSubviews = YES;

    self.contentView.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
    self.contentView.contentMode = UIViewContentModeScaleToFill;
    self.contentView.autoresizesSubviews = YES;

    [self.imageView drawRect:CGRectMake(0.0f, 0.0f, 320.0f, 0.0f)];
    self.imageView.contentMode = UIViewContentModeScaleAspectFill;
    self.imageView.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
    self.imageView.opaque = YES;
}
return self;
}

ImageDownloader (implements SDWebImageManagerDelegate)

    -(void) downloadImage // Comes from Model class
    {    
    if (image == nil)
    {
        NSURL *url = [NSURL URLWithString:self.urlString];

        SDWebImageManager *manager = [SDWebImageManager sharedManager];

        // Remove in progress downloader from queue
        [manager cancelForDelegate:self];

        if (url)
        {   
            [manager downloadWithURL:url delegate:self retryFailed:YES];
        }
    }
    }

    - (void)cancelCurrentImageLoad
    {
        [[SDWebImageManager sharedManager] cancelForDelegate:self];
    }

    - (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)_image
    {      
        self.image = _image;

        if ([self.delegate respondsToSelector:@selector(addImageToModel:)]) [self.delegate addImageToModel:self];
    }
    - (void)webImageManager:(SDWebImageManager *)imageManager didFailWithError:(NSError *)error;
    {
        if ([self.delegate respondsToSelector:@selector(badImage)]) [self.delegate badImage];
    }
like image 234
daihovey Avatar asked Jul 27 '11 11:07

daihovey


3 Answers

After you download the images, dont keep the large images in memory. just create a small size of image(thumbnail) to display in the tableview and write the larger image to some directory.

you can create a thumbnail of your image using the following code.

            CGSize size = CGSizeMake(32, 32);
            UIGraphicsBeginImageContext(size);
            [yourImage drawInRect:CGRectMake(0, 0, 32, 32)];
            yourImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
like image 179
ArunGJ Avatar answered Nov 26 '22 06:11

ArunGJ


Instead of using [UIImage imageNamed:@""] , try [[[UIImage alloc] initWithContentsOfFile:@""] autorelease];

Edit:

Fine. I have gone through the SDWebImage. Use NSAutoreleasePool wherever you find that a new thread has been spawned.

And one more solution would be, resize the image before saving to cache.

like image 27
Ilanchezhian Avatar answered Nov 26 '22 07:11

Ilanchezhian


So basically once the image is downloaded it stays in it's SDWebImage instance and never gets released. You should save your image to iPhone's disk with:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
NSString *documentsPath = [paths objectAtIndex:0];
NSString *imagePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"image%d.jpg", rowNumber]; // you should have this set somewhere
[UIImageJPEGRepresentation(_image, 1.0) writeToFile:imagePath atomically:YES];

and keep just the path to it in your SDWebImage instance. Then in -cellForRowAtIndexPath: method instead of doing:

cell.imageView.image = download.image;

you should do something like:

UIImage *image = [[UIImage alloc] initwithContentsOfFile:download.imagePath];
cell.imageView.image = image;
[image release];

This will always load the image from the disk and since cell.imageView.image is a retained property, once it get's niled or reused, it will clean up the image from memory.

like image 27
Filip Radelic Avatar answered Nov 26 '22 06:11

Filip Radelic