UITableView scrolling is not smooth

Alexey Strakh picture Alexey Strakh · Oct 11, 2014 · Viewed 16.8k times · Source

I have the smooth scrolling issue at my UITableView with UITableViewCell which contains UIImageView. Similar issues could be found all over the StrackOverflow but none of the proposed solutions helped me to completely get rid of the lag.

My case is quite common:

  1. images are stored at application storage (in my sample at app bundle)
  2. images could have different size (500x500, 1000x1000, 1500x1500)
  3. I need to display those images in UITableView where UIImageView size is 120x120 (retina)

I have followed multiple optimization tips and managed to optimize scrolling a lot. Unfortunately it is still not perfect. This is my scenario:

  1. first I moved all the image loading/processing/resizing logic to the background thread
  2. UITableViewCell reuse is enabled
  3. once UITableViewCell is in view I clear old values (settings to null) and start background thread to load the image
  4. at this point we are in background thread and I'm adding 500 ms delay to avoid settings new image to often (in case we are scrolling fast) (see below explanation)
  5. if UIImage exists at static image cache (regular dictionary with UIImage instances) - fetch that one and go to the step 9.
  6. if not - load new image from bundle (imageWithName) using url to app bundle (in real world scenario images will be stored to application storage, not bundle)
  7. once image is loaded resize it to 120x120 using graphics context
  8. save resized image to the static image cache
  9. at this point we have instance to UIImage and process is in the background thread. From here we move back to UI Thread with the given image
  10. if data context was cleared (for example UITableViewCell disappeared or was reused to display another image) we skip processing of the currently available image.
  11. if data context is the same - assign UIImage to UIImageView with an alpha animation (UIView.Animate)
  12. once UITableViewCell is out of view - clear the data context

Originally before starting new background thread to fetch the image here (step 1) was UIImage cache check without background thread. In this case if we have the image in the cache we assign it instantly and this introduces a great lag during fast scrolling (we assign images to often as long as we fetch them instantly). Those lines are commented at my example attached below.

There are still two issues:

  1. at some point during scrolling I still have a small lag (at the moment when I'm assign new UIImage to UIImageView.
  2. (this one is more noticeable) when you tap on item and go back from details there is a lag right before back navigation animation is finished.

Any suggest how to deal with those two issues or how to optimize my scenario are appreciated

Please take into account that sample written in Xamarin but I don't believe that Xamarin is the cause of the problem as long as I have the same issue for the app written in ObjectiveC as well.

Smooth Scrolling Test App

Answer

longi picture longi · Oct 29, 2014

Did you every tried to populate your TableView with only one 120x120 Image which is saved in your Bundle? This way you can check, if the problem occurs of your Image rendering

Instead of resizing all your images to 120x120 and save them in cache, I would recommend creating and using a thumbnail of all your images. You are somehow already doing this, but you are doing this couple of times (everytime you are scrolling or if your cache is full).

In our last project we had a UICollectionView with book covers. Most of the covers were between 400-800kb big and the feeling while scrolling was really bad. So we created a thumbnail for each image (thumbails about 40-50kb) and used the thumbnails instead of real covers. Works like a charm! I attached the thumbnail creation function

- (BOOL) createThumbnailForImageAtFilePath:(NSString *)sourcePath withName:(NSString *)name {

    UIImage* sourceImage = [UIImage imageWithContentsOfFile:sourcePath];
    if (!sourceImage) {
        //...
        return NO;
    }

    CGSize thumbnailSize = CGSizeMake(128,198);

    float imgAspectRatio = sourceImage.size.height / sourceImage.size.width;
    float thumbnailAspectRatio = thumbnailSize.height/thumbnailSize.width;

    CGSize scaledSize = thumbnailSize;

    if(imgAspectRatio >= thumbnailAspectRatio){
         //image is higher than thumbnail
         scaledSize.width = scaledSize.height * thumbnailSize.width / thumbnailSize.height;
    }
    else{
        //image is broader than thumbnail
        scaledSize.height = scaledSize.width * imgAspectRatio;
    }

    UIGraphicsBeginImageContextWithOptions( scaledSize, NO, 0.0 );
    CGRect scaledImageRect = CGRectMake( 0.0, 0.0, scaledSize.width, scaledSize.height );
    [sourceImage drawInRect:scaledImageRect];
    UIImage* destImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    NSString* thumbnailFilePath = [[self SOMEDIRECTORY] stringByAppendingPathComponent:name];

   BOOL success = [UIImageJPEGRepresentation(destImage, 0.9) writeToFile:thumbnailFilePath atomically:NO];

return success;

}