UITableView with images display wrong image for a moment while scrolling

2.5k views Asked by At

I have a UITableView which has many rows and in every row there is only an images. To prevent lag I used code below. But now while Im scrolling it displays photos from previous rows for a moment then corrects itself. How to solve that issue?

- (void)loadImageNamed:(NSString *)name {
    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
        // Determine path to image depending on scale of device's screen,
        // fallback to 1x if 2x is not available
        NSString *pathTo1xImage = [[NSBundle mainBundle] pathForResource:name ofType:@"jpg"];

        NSString *pathToImage = pathTo1xImage;

        UIImage *uiImage = nil;

        if (pathToImage) {
            // Load the image
            CGDataProviderRef imageDataProvider = CGDataProviderCreateWithFilename([pathToImage fileSystemRepresentation]);
            CGImageRef image = CGImageCreateWithJPEGDataProvider(imageDataProvider, NULL, NO, kCGRenderingIntentDefault);


            // Create a bitmap context from the image's specifications
            // (Note: We need to specify kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little
            // because PNGs are optimized by Xcode this way.)
            CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
            CGContextRef bitmapContext = CGBitmapContextCreate(NULL, CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerComponent(image), CGImageGetWidth(image) * 4, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);


            // Draw the image into the bitmap context
            CGContextDrawImage(bitmapContext, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);

            //  Extract the decompressed image
            CGImageRef decompressedImage = CGBitmapContextCreateImage(bitmapContext);


            // Create a UIImage
            uiImage = [[UIImage alloc] initWithCGImage:decompressedImage];


            // Release everything
            CGImageRelease(decompressedImage);
            CGContextRelease(bitmapContext);
            CGColorSpaceRelease(colorSpace);
            CGImageRelease(image);
            CGDataProviderRelease(imageDataProvider);
        }


        // Configure the UI with pre-decompressed UIImage
        dispatch_async(dispatch_get_main_queue(), ^{
            self.categoryImageLabel.image = uiImage;
        });
    });
}
4

There are 4 answers

3
Duncan C On BEST ANSWER

The other answers tell you what to do, but not why.

Think of a cell like the form you have to fill out in a doctor's office waiting room. Imagine that the office reuses those forms, and each patient has to erase ALL the data on the form before filling out their information.

If you don't have any allergies, you might be tempted to skip the allergies section since they don't apply to you. However, if the last person had allergies, and you didn't erase their answers, their answers would still show up on the form.

Likewise, when you dequeue a recycled cell, you have to clear out ALL the fields, even ones that don't apply to you. You should set images to nil or to their starting placeholder value.

Note that if you load data asynchronously, you should still reset fields to their default values first, since the old value will show up until the async load completes. That's what's happening in your case.

You can either set the image to nil/placeholder in cellForRowIndexPath, or in prepareForReuse, but you need to reset it in one of those places or you'll see the leftover image until the new one finishes loading.

1
Nimit On

In your custom cell write this:

- (void)prepareForReuse {
   [super prepareForReuse];

   self.uiImage.image = nil;
} 
0
Abdoelrhman On

Your old images is being reused by the tableview, which means if the imageview has an image it will be the same when the view recycle it, so you've to nil it or replace it with a place holder image if you've one.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = CustomUITableViewCell
  cell.imageview.image = nil
  // load your image
}

or on your custom cell call prepare for reuse which get called in every time the cell gets reused by the table:

 override func prepareForReuse() {
   super.prepareForReuse()
   imageView.image = nil // or place holder image
 }
0
Ali On

Even though the accepted answer works but it's not the recommended way.

As per Apple's documentation, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state.

So the best way to handle this issue is having a default image that is set in cell(for row:atIndexPath:) so that it takes care of both the cases where you have the image and resorts to default image if the image is not available, what this will do is when you're scrolling fast. it wouldn't have loaded the images yet and in the reuse behaviour of a cell it will pick up the default image.