11

I have a custom UITableViewCell that consists of a UIImageView and a UILabel. The cell is 320x104px and the imageView takes up the whole area with the label in front. There are only 8 cells.

in ViewDidLoad I am creating all needed images up front and caching them in a dictionary at the correct dimensions.

When I scroll the UITableView there is a noticable lag every time a new cell is encountered. This makes no sense to me as the image it is using is already created and cached. All that I'm asking of the cell is for its UIImageView to render the image.

I am using a custom cell with its view in a xib and configuring my UITableView to use it with:

[self.tableView registerNib:[UINib nibWithNibName:@"ActsCell" bundle:nil] forCellReuseIdentifier:myIdentifier];

Cell creation and configuration:

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{  
    NSString* reuseIdentifier = @"ActsCell";
    ActsCell* cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
    // Configure the cell...
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

- (void)configureCell:(ActsCell *)cell atIndexPath:(NSIndexPath *)indexPath 
{
    Act* act = [self.acts objectAtIndex:indexPath.row];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.title.text = act.name;
    cell.imageView.image = [self.imageCache objectForKey:act.uid];
}

What could be causing the lag? There would seem to be no benefit in trying to do anything Async as all the time-intensive work is done.

Undistraction
  • 42,754
  • 56
  • 195
  • 331
  • I can think of two things. One is that dequeueing fails because you don't set the `reusableIdentifier` properly. The other is the size of the images. Even though the images are loaded, CG needs to process the image data before showing, which may involve costly operations like scaling, compositing etc. – Lvsti May 02 '12 at 17:38
  • @Lvsti Can you elaborate on how I'm not setting the reuseIdentifier properly? – Undistraction May 04 '12 at 11:02
  • you're right, that's probably not the case, otherwise you won't get any cells back when dequeueing... – Lvsti May 04 '12 at 12:50
  • If you mean I don't use if(cell == nil){ //Create cell }, that is not required if using registerNib:forCellReuseIdentifier: as cell creation is deferred to the VC. – Undistraction May 04 '12 at 13:08
  • yeah I realized that. As for the images, what size are they of? – Lvsti May 04 '12 at 14:26
  • it may be worth running the profiler with the CPU and Core Graphics tools to see where the most cycles are spent – Lvsti May 04 '12 at 16:38

1 Answers1

34

Do you load images from local files by any chance?

By using Instruments I've found that there is some lazy loading mechanism in UIImage - the real image data was decompressed from PNG only at the stage of rendering it on the main thread which caused the lag during scrolling.

So, after the loading of UIImage with -initWithContentsOfFile: method, I've added code to render the contents of this image to offscreen context, saved that context as new UIImage and used it for UIImageView in UITableViewCell, this made the scroll to be smooth and pleasant to the eye again.

In case of reference there is the simple code I'm using to force reading of image contents in separate thread (using ARC):

UIImage *productImage = [[UIImage alloc] initWithContentsOfFile:path];

CGSize imageSize = productImage.size;
UIGraphicsBeginImageContext(imageSize);
[productImage drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
productImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

And I think the surface of UIImage created in such a way will be in the supported format suited for rendering, which will also offload work needed to render it on main thread.

EDIT: The docs for UIGraphicsGetImageFromCurrentImageContext() say that it should be only used from main thread, but searching on the net or SO shows that starting from iOS 4 the UIGraphics.. methods became thread safe.

zubko
  • 1,718
  • 25
  • 28
  • 5
    This was absolutely invaluable to me. I had been suffering from choppy scrolling on a UICollectionView with UIImageViews in each cell to display a photo. I tried *a lot* of different solutions but the only way I get close to 60FPS was extremely hacky. Most of the solutions I tried involved preloading a NSCache of UIImages using imageWithContentsOfFile but whatever I did there was always a little jump as new cells came on screen until I implemented your code above. As you say, there must be some sort of lazy loading mechanism built into imageWithContentsOfFile which isn't mentioned in the docs. – Alistair Holt Jan 02 '13 at 16:00
  • This work for me, the much smoother after store the UIImage instead of downloaded data then convert to UIImage each time it will be used. Thank you a lot. – Envil Aug 30 '13 at 03:19
  • Been searching so long for the slow/choppy cause. You sir are a hero – user1838169 Sep 06 '13 at 10:49
  • Sorry for bringing up and old thread, but I have a similar problem, but I cant figure out how/ where to use the above code in cellForRowAtIndexPath – Tassos Voulgaris Oct 23 '13 at 12:40
  • 1
    The above code isn't for `cellForRowAtIndexPath`. Any scenario of smooth scrolling of cells with lots of data (images in this case) is about moving the heaviest part of loading to separate thread. And this code is for the parallel worker thread, it will make sure that the image was loaded in worker thread and not in the main UI thread. ---- But you should learn first how to load images for table view cells in the separate thread, that's different topic. – zubko Oct 23 '13 at 16:51
  • Thanks for your response. Do you know of any examples? – Tassos Voulgaris Oct 24 '13 at 10:47
  • For me scrolling was jumpy even loading asynchronously. Until I added this. I was like magic. Thanks! – dherrin79 Aug 10 '14 at 18:10