18

I have a UITableView with about 400 cells in 200 sections and it's a little sluggish in responding to user interaction (scrolling, selecting cells.) I've made sure the methods for retrieving cells and header views do the bare minimum as it's running, and I don't think I'm doing anything out of the ordinary to make it slow. The cells and headers just have a background image and text. Has anyone else had this kind of problem, and do you know any way to make it run a little faster?

Edit: I'm offering a bounty because I'd love to get some useful feedback on this. I don't think the answer lies in a problem in my code. Instead I'm looking for strategies to re-engineer the UITableView so that it runs faster. I'm totally open to adding new code and I look forward to hearing what you guys have to say.

Sluggishness is observed on both the simulator and my device, an iPhone 4. Here are my implementations of viewForHeaderInSection and cellForRowAtIndexPath, which are the only UITableViewDelegate methods implemented nontrivially. I am reusing cells and header views.

- (UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger) section
{
    HaikuHeaderView* view= [m_sectionViews objectAtIndex:section];
    NSMutableArray* array= [m_haikuSearch objectAtIndex:section];
    Haiku* haiku= [array objectAtIndex:0];

    [view.poetLabel setText:[haiku nameForDisplay]];

    return view;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];

        cell.backgroundView= [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cell gradient2.png"]];

        // (Set up a bunch of label attributes in the cell...)
    }

    NSMutableArray* array= [m_haikuSearch objectAtIndex:indexPath.section];
    Haiku* haiku = [array objectAtIndex:indexPath.row];
    cell.textLabel.text = [haiku.m_lines objectAtIndex:0];

    return cell;
}
Irfan
  • 4,301
  • 6
  • 29
  • 46
Luke
  • 7,110
  • 6
  • 45
  • 74
  • it would be helpful if you can show us how you have implemented those methods. Not the whole code but just the basic logic – Robin May 30 '11 at 04:04
  • What devices are you testing on? Or is the sluggishness also observed on your Mac? – BoltClock May 30 '11 at 04:13
  • Possible duplicate that answers your question: [Tricks for improving iPhone UITableView scrolling performance?](http://stackoverflow.com/questions/1352479/tricks-for-improving-iphone-uitableview-scrolling-performance) – PengOne Jun 22 '11 at 03:32
  • 4
    how about reading image once from file n using that object instead of reading from file for every time.... [UIImage imageNamed:@"cell gradient2.png"]]; – saurabh Jun 22 '11 at 06:45
  • See [my answer to this question](http://stackoverflow.com/questions/6404951/whats-the-most-efficient-way-to-handle-a-uibutton-photo-grid-in-a-uitableview/6405117#6405117), I give lots of details about optimizing your code for tableviews. HTH – AliSoftware Jun 21 '11 at 08:02

9 Answers9

52

Even if your cell is actually that simple (background image and label) there are some things to consider

Image caching This is the obvious thing - if you are using the same image everywhere, load it once into the UIImage and reuse it. Even if the system will cache it on its own, directly using the already loaded one should never hurt.

Fast calculation Another rather obvious thing - make calculating height and content as fast as possible. Don't do synchronous fetches (network calls, disk reads etc.).

Alpha channel in image What's expensive when drawing is transparency. As your cell background has nothing behind it, make sure that you save your image without alpha channel. This saves a lot of processing.

Transparent label The same holds true for the label on top of your background view, unfortunately making it opaque might ruin the looks of your cell - but it depends on the image.

Custom cell In general, subclassing UITableViewCell and implementing drawRect: yourself is faster than building the subview hierarchy. You might make your image a class variable that all instances use. In drawRect: you'd draw the image and the text on top of it.

Check compositing The simulator has a tool to highlight the parts that are render-expensive because of transparency (green is ok, red is alpha-blending). It can be found in the debug menu: "Color Blended Layers"

Eiko
  • 25,601
  • 15
  • 56
  • 71
  • Good point, make sure the views (table view, imageView, etc) are all opaque with a solid color (no alpha, no clearColor) ... you can't always get 100% on this and still have a beautiful interface, but you can eliminate 85% of the expensive transparency accommodations. That helped me a great deal. There's also a way to run Instruments with a CoreGraphics test to look for views with alpha areas ... – Greg Combs Jun 23 '11 at 19:21
  • Just another note regarding custom drawing... Drawing images is often faster when simply adding an UIImageView - trying and profiling is recommended. – Eiko Jun 26 '11 at 09:47
  • Thanks for all your help - drawing a shadow on the text using QuartzCore was the culprit. – Luke Jul 11 '11 at 16:57
  • "Instruments has a tool to highlight...". Mind telling us which tool? – Kevin Nov 27 '15 at 15:44
  • Exactly my question. Everyone keeps telling me to use Instruments – and I do. But where to look? – Martin Algesten Nov 30 '15 at 03:33
  • 1
    @MartinAlgesten It's now to be found directly in the simulator (debug menu). I added this to my answer. – Eiko Dec 03 '15 at 10:12
  • @Kevin It's now to be found directly in the simulator (debug menu). I added this to my answer. – Eiko Dec 03 '15 at 10:12
  • +1 for fast calculation; in my case, calculation of row height. Loading 800+ cells with a non-optimised calculation of row height based on text content was *extremely* slow. A quick test: using a fixed row height made the table view load almost instantaneous. Definitely something to look out for. – Gavin Hope Feb 16 '16 at 23:12
9

The best thing you can do if you're looking to speed up your code is to profile it. There are two reasons for this:

  1. You can read about some things that'll improve table performance in general, like using fixed-height cells and re-using cells, and it'll probably help to implement those things (looks like you've already done that). But when it comes to speeding up your code, you really need to know where you app is spending most of its time. It might be that there are a few methods that take a very long time, or a method that's relatively quick but gets called a lot more often than you'd expect.

  2. It's impossible to know whether the changes you make in an effort to speed things up truly make a difference unless you have some numbers to measure against. If you can show that your code was spending 80% of its time in one routine and you cut that down to 35%, you know you're making progress.

So, break out Instruments and start measuring. If you can, it's a good idea to measure while you're doing each of the different activities that you want to speed up... do one profiling session while scrolling, one while selecting as many different cells as you can in a fixed period, etc. Don't forget to save the results so you can compare later.

Caleb
  • 124,013
  • 19
  • 183
  • 272
5

Just note these points..

  1. Are you reusing the cells..Which is a good practice to do..
  2. Make sure you are not doing any expensive calculations in cellForRowAtIndexPath callback, or in a function called from CellForRowAtIndexPath..
  3. You said there is a background image. Another reason that you must reuse your cell.

Some good info about cell reuse is here..

EDIT : Found this page very late..

This SO question thread might help you...especially the accepted answer...

Community
  • 1
  • 1
Krishnabhadra
  • 34,169
  • 30
  • 118
  • 167
  • Thanks for the info, I realize I was pretty vague in my question but in part I'm looking for general advice on how people have made UITableViews faster. – Luke May 30 '11 at 17:20
3
  1. use a shared image instance for the background (you alloc/init/release one for every time a new cell is created). When your table view is big , this means that the background X cells in memory takes much more memory than it should.

    instead of
    cell.backgroundView= [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cell gradient2.png"]];
    just use :
    cell.backgroundView= [SomeHelperClass sharedBackgroundUIImageResource];

  2. If that doesn't help , use CG instead of labels and other subviews (a screenshot will help here.. to know what we're talking about).

Community
  • 1
  • 1
Nir Golan
  • 1,336
  • 10
  • 24
  • He's not allocating new images every time, only when a new cell is created. – EmilioPelaez Jun 23 '11 at 04:44
  • @Emilio.. that's what I meant. He could use a shared image. We dont know anything about "cell gradient2.png" , it could take 1MB AFAIK. – Nir Golan Jun 23 '11 at 05:20
  • The thing is: 'cell.backgroundView= [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cell gradient2.png"]];' results in a memory leak. The image should the the exact size/no alpha for this place. If the image is used already then it is cached. – Teodor Kostov Jun 25 '11 at 12:45
2

Does the table view's delegate implement:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

If so you may wish to consider setting your UITableViewCell's rowHeight property instead.

Andrew Ebling
  • 10,175
  • 10
  • 58
  • 75
  • sorry, what is the relation between rowHeight and sluggish tableView? – Krishnabhadra May 30 '11 at 04:45
  • @Kris Row height is called whenever a cell will be shown. If you do expensive calculations there, it will hurt the performance. – EmilioPelaez Jun 23 '11 at 04:46
  • @Emilio..That means @Andrew was saying about setting rowheight directly when cell is created instead of implementing this delegate..right? – Krishnabhadra Jun 23 '11 at 08:02
  • Indeed, by implementing that method, it automatically adds quite a bit of expense to your tableview drawing/scrolling. Try to make the row height static so that you can eliminate this method. That, in conjunction with many of the other answers here should prove extremely beneficial. – Greg Combs Jun 23 '11 at 19:17
  • This was the bottleneck for me. – Andrew Dec 10 '14 at 05:01
  • Setting `tableView.estimatedRowHeight` might help a little bit. For me it seems to render a little faster with that, but scrolling is still very choppy. – Andrew Dec 10 '14 at 05:07
1

Are you using a lot of subviews?

If so, a good technique is to, instead of adding a lot of labels and images, draw them using CoreGraphics.

To do this, you'd have to subclass UITableViewCell and implement the -(void)drawRect:(CGRect)rect method.

EmilioPelaez
  • 18,758
  • 6
  • 46
  • 50
1

Two suggestions: One is to use -initWithStyle:reuseIdentifier: for your table view cells instead of -initWithFrame:. The other is to comment out setting the cell.backgroundView to an image with a gradient and see if that's the culprit. Every time I've had poor performance in a table view it's been because of an image.

Drew C
  • 6,408
  • 3
  • 39
  • 50
0

This is a little off-topic here (because you have only one background image for all cells):

My app displays different images in each cell. After adding about 5 cells to UITableView - table is slowed down drastically. It takes about 1-2 seconds to process all images each time i open view controller.

if let image = UIImage(contentsOfFile: photoFile){
    // just set it and let system fit it
    //cell.imageView!.image = image

    // 1 - Calculate sized
    let DEFAULT_THUMBNAIL_WIDTH: CGFloat  = (cellHeight / 4) * 5;
    let DEFAULT_THUMBNAIL_HEIGHT: CGFloat = cellHeight;

    let aspectRatio: CGFloat = image.size.width / image.size.height
    var willBeHeight = DEFAULT_THUMBNAIL_HEIGHT
    var willBeWidth  = DEFAULT_THUMBNAIL_HEIGHT * aspectRatio

    if(willBeWidth > DEFAULT_THUMBNAIL_WIDTH){
        willBeWidth = DEFAULT_THUMBNAIL_WIDTH
        willBeHeight = willBeWidth / aspectRatio
    }

    let eps:CGFloat = 0.000001
    assert((willBeHeight - eps) <= DEFAULT_THUMBNAIL_HEIGHT);
    assert((willBeWidth - eps) <= DEFAULT_THUMBNAIL_WIDTH);

    // 2 - Create context
    var size:CGSize = CGSize(
        width: DEFAULT_THUMBNAIL_WIDTH,
        height: DEFAULT_THUMBNAIL_HEIGHT)
    UIGraphicsBeginImageContext(size)

    // one-to-one rect
    //var imageRect: CGRect = CGRectMake(0.0, 0.0, size.width, size.height)
    var imageRect: CGRect = CGRectMake(0.0, 0.0, willBeWidth, willBeHeight)

    // 3 - Draw image
    image.drawInRect(imageRect)
    var imageResult: UIImage = UIGraphicsGetImageFromCurrentImageContext()
    cell.imageView!.image = imageResult

    UIGraphicsEndImageContext()
}else{
    DDLogError("Can not draw photo: \(photoFile)")
}

So i've ended up with generating small THUMBNAILS for all my images.

Anthony Akentiev
  • 1,001
  • 11
  • 9
-1

Reuse cells. Object allocation has a performance cost, especially if the allocation has to happen repeatedly over a short period—say, when the user scrolls a table view. If you reuse cells instead of allocating new ones, you greatly enhance table-view performance. Avoid relayout of content. When reusing cells with custom subviews, refrain from laying out those subviews each time the table view requests a cell. Lay out the subviews once, when the cell is created. Use opaque subviews. When customizing table view cells, make the subviews of the cell opaque, not transparent.

Prerna
  • 369
  • 3
  • 13