Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView scrolling is not smooth

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

like image 790
Alexey Strakh Avatar asked Oct 11 '14 03:10

Alexey Strakh


People also ask

How do you optimize table views performance for smooth fast scrolling?

First off, the tableView(_:cellForRowAt:) method should be as fast as possible. This method is called every time a cell needs to be displayed. The faster it executes, the smoother scrolling the table view will be.

How can I improve my TableView performance?

Cache can basically solve most performance problems. TableView needs to know the height of the Cell to layout the Cell. You need to know the height of all the Cells to know the height of the TableView itself. Therefore, every time you call reloadData, you need to calculate the height of all the Cells.

Is UITableView scrollable?

UITableView is a subclass of UIScrollView that allows users to scroll the table vertically (the closely-related UICollectionView class allows for horizontal scrolling and complex two-dimensional layouts).

Can UITableView scroll horizontal?

You can add a UITableView to a UIScrollView and have it scroll horizontally.


2 Answers

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;

}

like image 129
longi Avatar answered Oct 08 '22 20:10

longi


Try facebook's Async Display library.

https://github.com/facebook/AsyncDisplayKit

Really easy to use.. from their guide: http://asyncdisplaykit.org/guide/

_imageNode = [[ASImageNode alloc] init];
_imageNode.backgroundColor = [UIColor lightGrayColor];
_imageNode.image = [UIImage imageNamed:@"hello"];
_imageNode.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f);
[self.view addSubview:_imageNode.view];

This decodes the image on a background thread.

I'm not sure if it's easy to use iOS libraries on Xamarin but if it's easy, give this a shot.

like image 29
Krys Jurgowski Avatar answered Oct 08 '22 21:10

Krys Jurgowski