2

I have a UIViewController with a UITableView that is loading incredibly slow (slow as molasses, perhaps) I can tell it's due to setting custom background images for UITableViewCells in my grouped UITableview owned by the ViewController.

I think I'm following as much as the recommendation noted in this other SO question: Tricks for improving iPhone UITableView scrolling performance

I've also followed this article to make sure I'm not doing something stupid: Cocoa With Love Custom UITableView Drawing

As soon as I don't invoke background styling code, the performance improves.

Following the suggestions, I'm doing all these steps:

  • Because of the way the designs are, I have 2 different cell variations and cannot have uniform row height. Regardless of the cell variation, the row background is always 1 of 3 images and is based on the row index. 0: top_row_image, n-1: bottom_row_image, all others: middle_row_image
  • I'm loading the images used for the background in my ViewController's viewDidLoad.
  • I don't have any drawRect code as I'm letting the UITextField inside the cell handle that
  • Cell's layer is set to opaque
  • Cell identifiers are being reused
  • Not loading the cell from a NIB file

Basically, I want to style the each of the table's section's rows with different top, middle and bottom row background images, depending on which type of row type it is. Can anyone suggest a better way to have custom backgrounds on a UITableView?

Here's my code:

ViewController:
@property (nonatomic, weak) IBOutlet                            *tableview;
@property (nonatomic, strong, readwrite) UIImage                *topRowImage;
@property (nonatomic, strong, readwrite) UIImage                *middleRowImage;
@property (nonatomic, strong, readwrite) UIImage                *bottomRowImage;

@synthesize tableview = _tableview;    
@synthesize topRowImage = _topRowImage;         
@synthesize middleRowImage = _middleRowImage;
@synthesize bottomRowImage = _bottomRowImage;

// Load the images that will be used for the cell background ahead of time
- (void)viewDidLoad
{
    [super viewDidLoad];        

    // All of the images are 60x20 but my cells are 300x44 or 300x56
    UIEdgeInsets edgeInsets = UIEdgeInsetsMake(2, 4, 2, 4);        
    self.topRowImage = [[UIImage imageNamed:@"top_row.png"] resizableImageWithCapInsets:edgeInsets];        

    UIEdgeInsets edgeInsets = UIEdgeInsetsMake(0, 4, 0, 4);
    self.middleRowImage = [[UIImage imageNamed:@"middle_row.png"] resizableImageWithCapInsets:edgeInsets];            

    edgeInsets = UIEdgeInsetsMake(2, 4, 2, 4);
    self.bottomRowImage = [[UIImage imageNamed:@"bottom_row.png"] resizableImageWithCapInsets:edgeInsets];    
}

- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellId = [self getCellIdAt:indexPath];

    BaseCustomTableViewCell *cell = (BaseCustomTableViewCell *)[aTableView dequeueReusableCellWithIdentifier:cellId];

    if (cell == nil) 
    {
        if ([cellId isEqualToString:@"CellId1"])
        {
           cell = [[CustomTableViewCell1 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];     
        }
        else
        { 
           cell = [[CustomTableViewCell2 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];     
        }
    }

    // the following line seems to be the bottleneck
    [self styleBackground:cell indexPath:indexPath totalRows:totalRows];

    [cell configureData:myRowData];
}

- (void)styleCellBackground:(BaseCustomTableViewCell *)cell 
                  indexPath:(NSIndexPath *)indexPath
                  totalRows:(NSInteger)totalRows
{
    UIImage *backgroundImage = nil;    

    if (indexPath.row == 0)
    {
        // Top row of this section        
        backgroundImage = self.topRowImage; // ivar loaded during 'viewDidLoad'
    }
    else if (indexPath.row == totalRows - 1)
    {
        // Bottom row of this section        
        backgroundImage = self.bottomRowImage;
    }
    else {
        // Middle row of this section        
        backgroundImage = self.middleRowImage;
    }

    [cell updateRowBackground:backgroundImage];
}

@implementation CustomTableViewCell

-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])
    {        
        // Create my text field
        _textField = [[UITextField alloc] initWithFrame:CGRectZero];        
        _textField.backgroundColor          = [UIColor whiteColor];    

        self.backgroundColor                = [UIColor whiteColor];            
        self.contentView.backgroundColor    = [UIColor whiteColor];

        self.backgroundView = [[UIImageView alloc] init];        

        // not sure which of these should be set to opaque to ensure to meet criteria #4
        // 4. Make your UITableViewCell's layer opaque (same goes for the content view if you have one)
        self.backgroundView.opaque = YES;
        self.layer.opaque = YES;
        self.opaque = YES;        
        self.contentView.opaque = YES;        

        [self.contentView addSubview:_textField];
    }
}

- (void)layoutSubviews
{    
    CGRect textFieldFrame = CGRectMake(10, 2, 278, 40);
    self.textField.frame = textFieldFrame;

    [super layoutSubviews];
}

- (void)updateRowBackground:(UIImage *)rowBackground
{
    ((UIImageView *)self.backgroundView).image = rowBackground;        
}
Community
  • 1
  • 1
Howard Spear
  • 551
  • 1
  • 5
  • 14
  • I'm confused but every cell has a different background? Around how many cells do you have? If you have many cells with diff background each then they should be dynamically loaded. By loading everything at the viewdidload you are making the interface wait for everything to be ready. Also if you are debuging and you have many cells make sure you dont NSLOG stuff that increases the loading time A LOT. – Pochi Sep 11 '12 at 00:58
  • I have 3 different background types. Top row cell of each section should have 1 type (it has a downward curve), the middle row has another (standard image) and finally, the bottom row of each section has a 3rd type (this image has an upward curve). – Howard Spear Sep 11 '12 at 01:00
  • I have a total of 15 rows, split into 4 sections. – Howard Spear Sep 11 '12 at 01:43

1 Answers1

0

I think you'll see a significant performance gain if you stop switching the background image of every single cell on every call to cellForRowAtIndexPath.

It sounds like you have three kinds of cells ("top", "middle", and "bottom"). You can stop reusing them interchangeably and therefore having to reset their background images on every use.

Instead you can init instances of your table cell with different identifiers depending on their position in the table. That way you'll create a single cell with a "top" identifier, a single cell with a "bottom" identifier, and many cells with a "middle" identifier. You can then set their background image only when you initialize the cell. As long as you attempt to reuse a cell with an appropriate identifier you'll no longer need to change their background images every time.

Jonah
  • 17,918
  • 1
  • 43
  • 70
  • Since I have 2 different cell types, I would then have 6 cell id variations. I was under the impression that having that many different cell types is also not great for performance. In the code sample that's available on the CocoaWithLove site, I only saw 1 cellId type but different background based on indexPath.row. – Howard Spear Sep 11 '12 at 01:10
  • More cell identifiers will probably result in the table view keeping more instances of the cells in memory but that is not necessarily going to impact performance. I'm under the impression that Matt's example on CocoaWithLove is a demonstration of how to customize cell appearance, not necessarily a guide to highly performant cell creation. Since you seem to have a real performance problem now and have attributed it to the cost of updating those background images I think this is a reasonable experiment to try to remove the problem. – Jonah Sep 11 '12 at 16:41
  • Just want to make sure I understand this: when you have a custom top, middle and bottom row background, you would then have 3 different cell identifiers for each cell type to dequeue. If your cells hold heterogenous data or UI elements, you would then have additional unique cell id types per each cell type. – Howard Spear Sep 13 '12 at 17:27
  • @HowardSpear That seems likely but depends on the specifics of your app and how expensive configuring a cell for reuse proves to be. If it is cheap to reconfigure a cell (in the cell's `-prepareForReuse` and/or the datasource's `-cellForRowAtIndexPath:`) then there's no need to introduce more identifiers. I would use a few identifiers as possible, just measure performance to see when you might want to introduce a new identifier in order to trade some additional memory use for lower CPU load when reusing cells. Start with just enough that you don't have to change backgrounds and see if it helps – Jonah Sep 13 '12 at 20:37
  • What Jonah said is correct but apart from that we just observed that `resizableImageWithCapInsets` takes more time than `stretchableImageWithLeftCapWidth: topCapHeight:` and lags the table view scroll. I know its deprecated but... – JOA80 Oct 10 '12 at 09:27