Search code examples
iosiphoneuitableviewmasonry-ios-osx

Masonry in iOS UITableViewCell is very slow


I am working on an iOS app for the iPhone and have a UITableView with custom cells that have variable height. For the layout of the cells I am using Masonry: https://github.com/Masonry/Masonry. Everything is rendering to my liking, the only problem I have is that there is some lag when a new cell comes onto the screen, enough to seriously hinder the user experience. I did some testing on execution time and it seems that the bulk of this lag is inside of the function:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

The execution time is on the order of a tenth of a second on iPhone 5. Further, I have found that most of this time happens in the function:

-(void)updateConstraints;

of my custom UITableViewCell class. In this function I set various constraints of views in the cell using Masonry. So I have essentially narrowed down this lag to Masonry calls. If you are wondering, the whole function looks like this:

-(void)updateConstraints
{
    [super updateConstraints];

    [self.thumbnailImageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.contentView.mas_top).offset(5);
        make.left.equalTo(self.contentView.mas_left).offset(5);
        make.right.equalTo(self.contentView.mas_right).offset(-5);
        make.height.equalTo(@(self.thumbnailImageView.frame.size.width)).with.priorityHigh();

    }];

    [self.likeButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.thumbnailImageView.mas_bottom).offset(5);
        make.left.equalTo(self.captionTextView.mas_right);
    }];

    [self.dislikeButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.thumbnailImageView.mas_bottom).offset(5);
        make.left.equalTo(self.likeButton.mas_right);
        make.right.equalTo(self.contentView.mas_right).offset(5);
    }];

    [self.captionTextView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.thumbnailImageView.mas_bottom).offset(5);
        make.left.equalTo(self.contentView.mas_left);
        make.right.equalTo(self.likeButton.mas_left);
        make.height.equalTo(@([self captionHeight]));
    }];

    [self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.greaterThanOrEqualTo(self.captionTextView.mas_bottom).offset(5).with.priorityHigh();
        make.bottom.equalTo(self.contentView.mas_bottom);
        make.left.equalTo(self.contentView.mas_left).offset(5);
    }];

    [self.scoreLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.captionTextView.mas_right);
        make.top.equalTo(self.likeButton.mas_bottom);
        make.right.equalTo(self.contentView.mas_right);
    }];

    [self.numCommentsLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.greaterThanOrEqualTo(self.scoreLabel.mas_top);
        make.bottom.equalTo(self.contentView.mas_bottom);
        make.right.equalTo(self.contentView.mas_right);
    }];
}

What I am wondering is if there is any way to reduce the execution time and remove this noticeable lag. The constraints that I am setting for each cell are the exact same constraints for every cell, with a few exceptions. Is this just an issue with AutoLayout or is something very wrong to what I am doing? overall is this just a bad approach?


Solution

  • After more exploration, I found that the root cause of the problem was that my cells were not being properly recycled. My cellForRowAtIndexPath function originally looked like this:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
    {
        static NSString *postTableIdentifier = @"PostTableCell";
    
        PostTableViewCell *cell = (PostTableViewCell *)[tableView dequeueReusableCellWithIdentifier:postTableIdentifier];
        if (cell == nil)
        {       
            NSArray *nib = [[NSBundle mainBundle] loadNibNamed:postTableIdentifier owner:self options:nil];
            cell = [nib objectAtIndex:0];
        }
    
        //rest of function...
    }
    

    Here the tableView looks for cells with a reuseIdentifier of @"PostTableCell". However, the loadNibNamed: function does not set the cell's reuseIdentifier, and so a dequeReusableCellWithIdentifier: could not find recycled cells. I found a good solution to this problem here: How can I recycle UITableViewCell objects created from a XIB? I will let it answer for itself, but my new code looks like this:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
    {
        static NSString *postTableIdentifier = @"PostTableCell";
    
        PostTableViewCell *cell = (PostTableViewCell *)[tableView dequeueReusableCellWithIdentifier:postTableIdentifier];
        if (cell == nil)
        {
            //register nib once
            [tableView registerNib:[UINib nibWithNibName:postTableIdentifier bundle:nil] forCellReuseIdentifier:postTableIdentifier];
            cell = (PostTableViewCell *)[tableView dequeueReusableCellWithIdentifier:postTableIdentifier];
        }
    
        //rest of function...
    }