Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView performance issues when adding UIViews to cell.contentView

I am experiencing performance problems when using some subviews on my UITableViewCells. After I keep scrolling it eventually starts getting very slow.

First step I am doing is creating a common UIView for every cell, essentially this is creating a white cell with a rounded effect on the cell with a shadow. The performance for this seems to be normal so I don't think it's the culprit.

enter image description here

Here is the code I am using to do this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
        static NSString *NewsCellIdentifer = @"NewsCellIdentifier";


       NewsItem *item = [self.newsArray objectAtIndex:indexPath.row];


            UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NewsCellIdentifer];

            if (cell == nil)
            {
                cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NewsCellIdentifer];


                cell.contentView.backgroundColor = [UIColor clearColor];

                UIView *whiteRoundedCornerView = [[UIView alloc] initWithFrame:CGRectMake(10,10,300,100)];
                whiteRoundedCornerView.backgroundColor = [UIColor whiteColor];
                whiteRoundedCornerView.layer.masksToBounds = NO;
                whiteRoundedCornerView.layer.cornerRadius = 3.0;
                whiteRoundedCornerView.layer.shadowOffset = CGSizeMake(-1, 1);
                whiteRoundedCornerView.layer.shadowOpacity = 0.5;

                [cell.contentView addSubview:whiteRoundedCornerView];
                [cell.contentView sendSubviewToBack:whiteRoundedCornerView];

                cell.layer.shouldRasterize = YES;
                cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
                cell.layer.opaque = YES;

                cell.opaque = YES;    
            }

            [cell.contentView addSubview:[self NewsItemThumbnailView:item]];

            return cell;
}

Here is the method that returns the thumbnail view of the graphic and text:

- (UIView *) NewsItemThumbnailView:(NewsItem *)item
{
    UIView *thumbNailMainView = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 50, 70)];
    UIImageView *thumbNail = [[UIImageView alloc] initWithImage:[UIImage imageNamed:item.ThumbNailFileName]];
    thumbNail.frame = CGRectMake(10,10, 45, 45);
    UILabel *date = [[UILabel alloc] init];
    date.frame = CGRectMake(10, 53, 45, 12);
    date.text = item.ShortDateString;
    date.textAlignment = NSTextAlignmentCenter;
    date.textColor = [BVColors WebDarkGrey];
    CGFloat fontSize = 10.0;
    date.font = [BVFont Museo:&fontSize];

    date.opaque = YES;
    thumbNail.opaque = YES;
    thumbNailMainView.opaque = YES;


    [thumbNailMainView addSubview:thumbNail];
    [thumbNailMainView addSubview:date];

    return thumbNailMainView;
}

The performance problem seems to be when I add the thumbnail view to the cell because when I comment that line out, I don't seem to have it. The thumbnail information is dynamic and will change with each cell. I would appreciate any advice on how I should do this without degrading the performance.

like image 972
Flea Avatar asked Feb 17 '23 15:02

Flea


2 Answers

UITableView will call tableView:cellForRowAtIndexPath: each time a cell comes into view, and dequeueReusableCellWithIdentifier: will reuse existing cell objects if they are available. These two facts combine to put you in a scenario where every time you scroll, the same finite number of cell objects end up with an increasing number of subviews.

The proper approach is to create a custom UITableViewCell subclass that has a property for thumbnailView. In the setter for that property, remove the previous thumbnail (if any) and then add the new one to the contentView. This ensures that you'll only ever have one thumbnail subview at any time.

A less optimal approach would be adding a tag to the UIView returned from NewsItemThumbnailView (thumbNailMainView.tag = someIntegerConstant) and then searching for any view with that tag and removing it before adding another:

 // remove old view
 UIView *oldThumbnailView = [cell.contentView viewWithTag:someIntegerConstant];
 [oldThumbnailView removeFromSuperview];

 // add new view
 [cell.contentView addSubview:[self NewsItemThumbnailView:item]];
like image 138
jszumski Avatar answered May 18 '23 21:05

jszumski


I ended up leveraging a solution found on this stackoverflow post:

How should I addSubview to cell.contentView?

Essentially when the cell is first initialized I am setting the view as mentioned by Nishant; however once the cell is reused I am extracting out the items I need to change, such as an UIImageView and then a UILabel. Since these are pointers I can modify just what I need when I need to and the performance is fast again. Here is a abbreviated version of what I did.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *NewsCellIdentifer = @"NewsCellIdentifier";

    NewsItem *item = [self.newsArray objectAtIndex:indexPath.row];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NewsCellIdentifer];


    UIView *thumbNailMainView = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 50, 70)];
    UIImageView *thumbNail;

    UIView *textMainView = [[UIView alloc] initWithFrame:CGRectMake(20,20,80,80)];
    UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(52,-5, 70, 20)];
    UILabel *teaserLabel = [[UILabel alloc] initWithFrame:CGRectMake(50,20, 210, 40)];

    UIView *newsItemCornerMainView = [[UIView alloc] initWithFrame:CGRectMake(255.7, 55.2, 55, 55)];
    UIImageView *cornerIconView;

    // If the cell doesn't existing go ahead and make it fresh.
    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NewsCellIdentifer];


        // Configure all the various subviews

         .....   //Sample below

        // Make the title view
        headerLabel.text = item.Title;
        CGFloat textfontSize = 16.0f;
        headerLabel.font = [BVFont Museo:&textfontSize];
        headerLabel.textColor = [BVColors WebBlue];
        headerLabel.textAlignment = NSTextAlignmentLeft;
        headerLabel.numberOfLines = 0;
        headerLabel.tag = 50;
        // Make the Teaser view
        teaserLabel.text = item.Teaser;
        teaserLabel.numberOfLines = 0;
        CGFloat tfontSize = 13.0f;
        teaserLabel.textAlignment = NSTextAlignmentLeft;
        teaserLabel.textColor = [BVColors WebDarkGrey];
        teaserLabel.font = [BVFont HelveticaNeue:&tfontSize];
        [teaserLabel sizeToFit];
        teaserLabel.tag = 51;
        [textMainView addSubview:headerLabel];
        [textMainView sendSubviewToBack:headerLabel];
        [textMainView addSubview:teaserLabel];
        [cell.contentView addSubview:textMainView];

        ....
    }

    thumbNail = (UIImageView *) [cell viewWithTag:47];
    [thumbNail setImage:[UIImage imageNamed:item.ThumbNailFileName]];

    headerLabel = (UILabel *) [cell viewWithTag:50];
    headerLabel.text = item.Title;
    teaserLabel = (UILabel *) [cell viewWithTag:51];
    teaserLabel.text = item.Teaser;

    cornerIconView = (UIImageView *) [cell viewWithTag:48];
    [cornerIconView setImage:[UIImage imageNamed:item.CornerIconFileName]];

    return cell;
}
like image 39
Flea Avatar answered May 18 '23 21:05

Flea