Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trial
Erik Montes
3,717 PointsUITableViewCell image repeats
I'm using the same technique as was used in the Ribbit app to load images asynchronously using GDC, however, if I scroll quickly up or down, then stop, the image will flip through a series of images until finally showing the correct image.
I kind of have a feeling what the problem is, but don't know how to fix it. Since the cells are reuseable, as I scroll up/down quickly it triggers a bunch of background calls to fetch and load images. If I scroll through say, 20 cells in 1 second, I think 20 requests will be called in the background and by the time I stop the scroll it will quickly display the images from those asynchronous requests it had done.
Here's what I have:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
WBItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSDictionary *item = [self.wishList objectAtIndex:indexPath.row];
cell.itemImage.image = nil;
cell.itemSubmitterAvatarImage.image = nil;
cell.itemTitleLabel.text = [item objectForKey:@"itemTitle"];
cell.itemSubmitterNameLabel.text = [item objectForKey:@"itemSubmitterName"];
cell.loadingImageIndicator.color = [UIColor grayColor];
cell.loadingImageIndicator.hidesWhenStopped = YES;
[cell.loadingImageIndicator startAnimating];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSURL *itemURL = [NSURL URLWithString:[item objectForKey:@"itemImageURL"]];
NSData *itemData = [NSData dataWithContentsOfURL:itemURL];
NSURL *avatarURL = [NSURL URLWithString:[item objectForKey:@"itemSubmitterAvatarURL"]];
NSData *avatarData = [NSData dataWithContentsOfURL:avatarURL];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.loadingImageIndicator stopAnimating];
cell.itemSubmitterAvatarImage.image = [UIImage imageWithData:avatarData];
cell.itemImage.image = [UIImage imageWithData:itemData];
});
});
//Styling
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
return cell;
}
6 Answers
John W
21,558 PointsThe first problem you probably want to fix is the flipping of images as they arrive asynchronously in random order on a slow network. In the GCD dispatch to main queue block, you can check to see if the cell is currently associating with the right item before replacing the image.
The second problem is the load time, which is partially responsible for the first problem. To fix this, you can cache the UIImage's in any collection class, but preferably a dictionary so you have an easy way to associate the cached images with the right rows. Set a max capacity so you don't run into any memory issues (and reimplement the didReceiveMemoryWarning method to lower it further when it does happen), and only dispatch a request for new images if they are not locally available.
Erik Montes
3,717 PointsHey John, thanks for the suggestions. I will try them out. How would you recommend that I "check to see if the cell is currently associating with the right item before replacing the image"? Is there a way I can identify an item with a certain cell?
Erik Montes
3,717 PointsI think probably using indexPath or indexPath.row?
John W
21,558 PointsCells don't have a property that ties them to their current indexPaths, but you can easily extend UITableViewCell without subclassing through category
Erik Montes
3,717 PointsJohn W I did an NSLog for the indexPath.row and it is an integer (first one is 1, second one is 2, etc.), so can't I use that integer to cache all the already fetched data in a dictionary for later use? Or would that be inefficient and prone to error? For example store all the fetch data for the first item and store it in a dictionary with the key of 1, then the next set of data with the key of 2, etc...
John W
21,558 PointsI'm assuming you were checking indexPath's value in tableview:cellForIndexPath:
Since the indexPath uniquely associates with a row (because that's how the tableview asks for the data), that is indeed a good key to use. However, the same cell can be associated with different indexPaths over time once scrolled out of the view and reused. You can check this yourself by checking/NSLogging the memory address of the cell with %p instead of %@. That's why I suggested the need to extend the cells with an additional property to tell which indexPath.row it is currently associated with.
And yes, a dictionary is the way to go in this case for a simplistic cache, but make it a NSMutableDictionary so you are not reallocating the whole space repeatedly. For more efficient caching, check out this github repository by the creators of the Path app: