How to tell when UITableView has completed ReloadData?

The reload happens during the next layout pass, which normally happens when you return control to the run loop (after, say, your button action or whatever returns).

So one way to run something after the table view reloads is simply to force the table view to perform layout immediately:

[self.tableView reloadData];
[self.tableView layoutIfNeeded];
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

Another way is to schedule your after-layout code to run later using dispatch_async:

[self.tableView reloadData];

dispatch_async(dispatch_get_main_queue(), ^{
     NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection:([self.tableView numberOfSections]-1)];

    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});

UPDATE

Upon further investigation, I find that the table view sends tableView:numberOfSections: and tableView:numberOfRowsInSection: to its data source before returning from reloadData. If the delegate implements tableView:heightForRowAtIndexPath:, the table view also sends that (for each row) before returning from reloadData.

However, the table view does not send tableView:cellForRowAtIndexPath: or tableView:headerViewForSection until the layout phase, which happens by default when you return control to the run loop.

I also find that in a tiny test program, the code in your question properly scrolls to the bottom of the table view, without me doing anything special (like sending layoutIfNeeded or using dispatch_async).


Swift:

    extension UITableView {
    func reloadData(completion:@escaping ()->()) {
        UIView.animate(withDuration: 0, animations: { self.reloadData() })
            { _ in completion() }
    } 
}

...somewhere later...

tableView.reloadData {
    print("done")
}

Objective-C:

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];

As of Xcode 8.2.1, iOS 10, and swift 3,

You can determine the end of tableView.reloadData() easily by using a CATransaction block:

CATransaction.begin()
CATransaction.setCompletionBlock({
    print("reload completed")
    //Your completion code here
})
print("reloading")
tableView.reloadData()
CATransaction.commit()

The above also works for determining the end of UICollectionView's reloadData() and UIPickerView's reloadAllComponents().