Search code examples
iosobjective-cuitableviewnimbus

UITableView with images display wrong image for a moment while scrolling


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;
        });
    });
}

Solution

  • 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.