Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Insert rows at top of UITableView without modifying scroll position when using UITableViewAutomaticDimension

How can one insert rows into a UITableView at the very top without causing the rest of the cells to be pushed down - so the scroll position does not appear to change at all?

Just like Facebook and Twitter when new posts are delivered, they are inserted at the top but the scroll position remains fixed.

My question is similar to this this question. What makes my question unique from that question is that I'm not using a table with fixed row heights - I'm using UITableViewAutomaticDimension and an estimatedRowHeight. Therefore the answers suggested there will not work because I cannot determine the row height.

I have tried this solution that doesn't involve taking row height into consideration, but the contentSize is still not correct after reloading, because the contentOffset set isn't the same relative position - the cells are still pushed down past where they were before the insert. This is because the cell hasn't been rendered on screen so iOS doesn't bother to calculate the appropriate height for it until it's about to appear, therefore contentSize is not accurate.

CGSize beforeContentSize = tableView.contentSize;
[tableView reloadData];
CGSize afterContentSize = tableView.contentSize;
CGPoint afterContentOffset = tableView.contentOffset;
tableView.contentOffset = CGPointMake(afterContentOffset.x, afterContentOffset.y + afterContentSize.height - beforeContentSize.height);

Alain pointed out rectForCellAtIndexPath which forces iOS to calculate the appropriate height. I can now determine the proper height for the inserted cells, but the scroll view's contentSize is still not correct, as is evidenced when I iterate over all cells and add up the heights which is a larger than contentSize.height. So ultimately when I set the contentOffset manually it's not scrolling to the correct location.

Original code:

//update data source
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:newIndexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];

In this scenario, what can be done to achieve the desired behavior?

like image 239
Jordan H Avatar asked Jan 12 '16 00:01

Jordan H


2 Answers

Late to the party but this works even when cell have dynamic heights (a.k.a. UITableViewAutomaticDimension), no need to iterate over cells to calculate their size, but works only when items are added at the very beginning of the tableView and there is no header, with a little bit of math it's probably possible to adapt this to every situation:

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        if indexPath.row == 0 {
            self.getMoreMessages()
        }
}

private func getMoreMessages(){
        var initialOffset = self.tableView.contentOffset.y
        self.tableView.reloadData()
        //@numberOfCellsAdded: number of items added at top of the table 
        self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: numberOfCellsAdded, inSection: 0), atScrollPosition: .Top, animated: false)
        self.tableView.contentOffset.y += initialOffset
}
like image 134
Azephiar Avatar answered Nov 14 '22 06:11

Azephiar


Also, single row sections can accomplish desired effect

If a section already exists at the specified index location, it is moved down one index location. https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/index.html#//apple_ref/occ/instm/UITableView/insertSections:withRowAnimation:

let idxSet = NSIndexSet(index: 0)
self.verseTable.insertSections(idxSet, withRowAnimation: .Fade)
like image 1
Loaf Box Avatar answered Nov 14 '22 07:11

Loaf Box