Vikings
Vikings

Reputation: 2527

Custom Table Cell Scrolling

I have a table view that displays custom cells, the cells contain an image which I am loading from the documents folder. I notice that when I scroll the table, there is some lag, which I am assuming is coming from loading the image from the disk. However, the image is already loaded at the point so i'm a little confused.

Suggestions on optimizing this code, would be appreciated. I have read about lazy loading, but i'm not sure if this applies to me or not.

I did check to make sure the table was reusing the cells.

Edit:

- (void)configureCell:(BeerCell *)cell 
          atIndexPath:(NSIndexPath *)indexPath 
{
    Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.displayBeerName.text = beer.name;

    // check to see if there is a cached image already.  Use a dictionary.
    // make sure this is one of your ivars
    __block UIImage *theImage = [self.imagesCache objectForKey: beer.imagePath];

    // If the image is not in your cache, you need to retrieve it.
    if (!theImage){
        // The image doesn't exist, we need to load it from disk, web or render it

        // First put a placeholder image in place.  Shouldn't be any penalties after the 
        // first load because it is cached.
        cell.beerImage.image = [UIImage imageNamed:@"beer-pic.png"];

        // check to see if your image cache dictionary has been created, if not do so now
        if (_imagesCache == nil) {
            _imagesCache= [[NSMutableDictionary alloc] initWithCapacity:1];
        }

        // get a weak reference to UITableViewController subclass for use in the block
        // we do this to avoid retain cycles
        __weak BeerListViewController *weakSelf = self;

        // do the heavy lifting on a background queue so the UI looks fast
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
        dispatch_async(queue, ^ {

            theImage = [UIImage imageWithContentsOfFile:beer.imagePath];

            // I added this in because I create the new core data object in this class, and pass
            // it to the class where I fill out the information about the beer
            if (theImage) {

                // Add the image to the cache
                [weakSelf.imagesCache setObject:theImage forKey:beer.imagePath];
                //[weakSelf.imagesCache addObject:theImage forKey:beer.imagePath];

                // Check to see if the cell for the specified index path is still being used
                BeerCell *theCell = (BeerCell *)[weakSelf.tableView cellForRowAtIndexPath:indexPath];
                // Per the docs. An object representing a cell of the table
                //  or nil if the cell is not visible or indexPath is out of range.
                if (theCell){
                    // dispatch onto the main queue because we are doing work on the UI
                    dispatch_async(dispatch_get_main_queue(), ^ {
                        theCell.beerImage.image = theImage;
                        [theCell setNeedsLayout];
                    });
                }
            }
        });
    }        
    else
    {
        // Image already exists, use it.
        cell.beerImage.image = theImage;
    }
}

Upvotes: 0

Views: 510

Answers (3)

timthetoolman
timthetoolman

Reputation: 4623

Whenever you load from disk, server, or render images for a tableview you will want to put it on a background queue. It is trivial to do so and will get great performance even on a 3GS.

I use a similar approach for generating thumbnails for tableviews and scroll views from very large images and the performance is very good.

Try this:

- (void)configureCell:(CustomCell *)cell 
          atIndexPath:(NSIndexPath *)indexPath 
{
    CoreDateObject *object = (CoreDateObject *)[self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.displayName.text = object.name;

    // check to see if there is a cached image already.  Use a dictionary.
    // make sure this is one of your ivars
    UIImage *theImage=[self.imagesCache objectForKey: object.imagePath];

    // If the image is not in your cache, you need to retrieve it.
    if (!theImage){
       // The image doesn't exist, we need to load it from disk, web or render it

       // First put a placeholder image in place.  Shouldn't be any penalties after the 
       // first load because it is cached.
       cell.selectedImage.image=[UIImage imageNamed:@"yourPlaceHolderImage"];

       // check to see if your image cache dictionary has been created, if not do so now
       if (_imagesCache==nil){
          _imagesCache=[NSMutableDictionary alloc] initWithCapacity:1];
       }

       // get a weak reference to UITableViewController subclass for use in the block
       // we do this to avoid retain cycles
       __weak YourTableViewControllerSubclass *weakSelf=self;

      // do the heavy lifting on a background queue so the UI looks fast
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
      dispatch_async(queue, ^{

              theImage=[UIImage imageWithContentOfFile:object.imagePath];

              // Add the image to the cache
              [weakSelf.imagesCache addObject:theImage forKey:object.imagePath];

              // Check to see if the cell for the specified index path is still being used
              CustomCell *theCell=(CustomCell *)[weakSelf.tableView cellForRowAtIndexPath:indexPath];
              // Per the docs. An object representing a cell of the table
              //  or nil if the cell is not visible or indexPath is out of range.
              if (theCell){
                 // dispatch onto the main queue because we are doing work on the UI
                 dispatch_async(dispatch_get_main_queue(), ^{
                     theCell.selectedImage.image=theImage
                     [theCell setNeedsLayout];
                 });
              }

    }else{
          // Image already exists, use it.
          cell.selectedImage.image=theImage;
    }

    cell.rating.rate = object.rating.doubleValue;
}

Upvotes: 1

TheTiger
TheTiger

Reputation: 13364

cellForRowAtIndexPath: is called each time when you scroll the table view ... So it can be hang your scrolling if you write large processing code in this method. So for this make an array of your images if possible then use thumbnail images.... then add those images to cell from array. I'm sure your scrolling will be smooth.

Upvotes: 0

DJPlayer
DJPlayer

Reputation: 3294

one of these two links will better explain how this is done properly..

https://developer.apple.com/library/ios/#samplecode/LazyTableImages/Introduction/Intro.html (official apple)

https://github.com/rs/SDWebImage (nice library to use)..

Upvotes: 0

Related Questions