Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableViewCell bad performance with AutoLayout

I'm somewhat stuck with this one… any help is very appreciated. I've already spent lots of time debugging this.

I've got UITableView with data source provided by NSFetchedResultsController. In a separate view controller I insert new records to the CoreData using [NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:], save the managed object context and dismiss that controller. Very standard stuff.

The changes in managed object context are then received by NSFetchedResultsController:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
        [self.tableView beginUpdates];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
        [self.tableView endUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    switch (type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationNone];

            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];

            break;

        case NSFetchedResultsChangeUpdate:
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];

            break;

        case NSFetchedResultsChangeMove:
            [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationNone];

            break;
    }
}

And this is where the problem appears — it takes too long(about 3-4 seconds on an iPhone 4) to do that. And it seems like the time is spent calculating layout for the cells.

I've stripped everything from the cell(including custom subclass) and left it with just UILabel, but nothing changed. Then I've changed the style of the cell to Basic(or anything except Custom) and the problem disappeared — new cells are added instantaneously.

I've doubled checked and NSFetchedResultsControllerDelegate callbacks are called only once. If I ignore them and do [UITableView reloadSections:withRowAnimation:], nothing changes — it is still very slow.

It seems to me like Auto Layout is disabled for the default cell styles, which makes them very fast. But if that is the case — why does everything loads quickly when I push the UITableViewController?

Here's the call trace for that problem: stack trace

So the question is — what is going on here? Why are cells being rendered so slowly?

UPDATE 1

I've built a very simple demo app that illustrates the problem I'm having. here's the source — https://github.com/antstorm/UITableViewCellPerformanceProblem

Try adding at least a screenful of cells to feel the performance problems.

Also note that adding a row directly ("Insert now!" button) is not causing any slowness.

like image 570
Anthony Dmitriyev Avatar asked Jun 05 '13 20:06

Anthony Dmitriyev


3 Answers

It is true that Auto Layout can present a performance hit. However, for most cases it is not really noticeable. There are some edge cases with complicated layouts where getting rid of it will make a meaningful difference, but that's not really the issue here.

I don't have a good explanation why the app is behaving the way it is, but at least I have a solution: Don't do table view updates if the table view is not on screen. This results in this weird behavior.

In order to this, you can e.g. check for self.tableview.window != nil in the delegate methods of the fetched results controller. Then you just need to add a [self.tableview reloadData] to viewWillAppear so that the table view updates its data before coming on screen.

Hope that helps. And please, if somebody has a good explanation for this weird behavior, please let me know :)

like image 64
Florian Kugler Avatar answered Oct 23 '22 06:10

Florian Kugler


Ok, I finally got around this problem without sacrificing animation. My solution is to implement UITableViewCell's interface in a separate Nib file with AutoLayout disabled. It takes a little bit longer to load and you need to positions subviews yourself.

Here's the code to make it possible:

- (void)viewDidLoad {
    [super viewDidLoad];

    ...        

    UINib *rowCellNib = [UINib nibWithNibName:@"RowCell" bundle:nil];
    [self.tableView registerNib:rowCellNib forCellReuseIdentifier:@"ROW_CELL"];
}

Of course you'll need a RowCell.nib file with your cell's view.

While there's no solution to the original problem(which clearly seems a bug to me), I'm using this one.

like image 22
Anthony Dmitriyev Avatar answered Oct 23 '22 06:10

Anthony Dmitriyev


I has the same performence in [table reloadData] in iOS 7. In my case, I solve this problem to replace the cell configure code from [cell layoutIfNeeded] to the follow code:

[cell setNeedsUpdateConstraints];
[cell setNeedsLayout];

and then return the cell. Evevthing seems ok. I hope this can help other people had the same problem.

like image 23
Jadian Avatar answered Oct 23 '22 08:10

Jadian