0

I'm trying to load images into a table but I am still getting the flickering. Images are loading, but when scrolling there is a short flicker before the next thumbnail image loads. Yes, know Apple has an example, and various frameworks, but this is super simple code, just that damn flickering before the next image is loaded. Everything else works fine. Thanks!

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"customCell";

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

if (cell == nil){
    NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:nil options:nil];

    for (id currentObject in topLevelObjects) {
        if ([currentObject isKindOfClass:[CustomCell class]]) {
            cell = (CustomCell *)currentObject;
            break;
        }
    }
}

NSString *myUrl = [[items objectAtIndex:indexPath.row] objectForKey:@"myUrl"];

NSURL *url = [NSURL URLWithString:url];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
    UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
    dispatch_sync(dispatch_get_main_queue(), ^{
        [[cell imageView] setImage:image];
        [cell setNeedsLayout];
    });
});

return cell;
}
Edward Potter
  • 3,760
  • 4
  • 28
  • 39
  • Your use of queues here is just going to be a waste of cycles. The image won't be decompressed into memory until it's active (when you set it using `-setImage:`), and the NSData won't actually read the disk until something asks for its bytes (which again, won't happen until `-setImage:` is called). So, by dispatching off and back, you're wasting time synchronizing and on top of that, all the hard work (disk access & decompression) are still done on the main thread. – Jason Coco Aug 27 '12 at 23:04

2 Answers2

1

You are using the reusing mechanism of UITableView. That means you can get a cell from dequeueReusableCellWithIdentifier which already has an image set. So at first you need to remove the image with [[cell imageView] setImage:nil]. Then inside the dispatch block you can't be sure that the cell is still on-screen. You need to check if the index path is still the same, otherwise you would set the image to a wrong cell:

dispatch_async(queue, ^{
    UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
    dispatch_sync(dispatch_get_main_queue(), ^{
        if ([[tableView indexPathForCell:cell] compare:indexPath] == NSOrderedSame) {
            [[cell imageView] setImage:image];
            [cell setNeedsLayout];
        }
// alternative:    
        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
        if (cell)
            cell.imageView.image = image;
    });
});

I recommend to use an NSOperation subclass to load the remote images instead of using dataWithContentsOfURL - then you can also cancel image loading when a cell goes offscreen. Also consider using an image cache for better performance.

Felix
  • 35,354
  • 13
  • 96
  • 143
  • Yes adding those lines stopped the flashing. But if i scroll fast i do see a white background then the image fills in quick. How would I pre-fetch the image, when i don't really have the URL until i first bring the cell into view. thanks – Edward Potter Aug 28 '12 at 15:35
  • you could start all URL requests in viewDidLoad (start multiple NSOperations) and cache the images, but I recommend to load the images when they are needed – Felix Aug 28 '12 at 16:30
  • Ok, guess where I'm confused as I do load images the second they are needed in the - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { method. Not sure if the user is going to look at 10 images or scroll through 300. So putting that code in ViewDidLoad may seem going overboard for something that may never occur. Works ok now, but if you scroll super fast, you do get a momentary white blank, then the image fills in. Like to preload the image right before it comes into view. That's the part i'm stuck on. – Edward Potter Aug 29 '12 at 14:29
  • i guess i could. but other apps seems to do it without a place holder. just i love HOW SIMPLE the block example works. Apple's example is like pages to do the same thing. Just like the SIMPLEST approach to make this work. Think I'm close. – Edward Potter Aug 30 '12 at 14:52
0

I had a similar issue you need to check to see if the image is already loaded. Your current code is just loading the images, but what if the cell already has an image? You do not want to perform this task every time the user scrolls the table.

Check out my questions I asked.

Table View Scrolling Async

Community
  • 1
  • 1
Vikings
  • 2,527
  • 32
  • 45