How to keep UITableView contentoffset after calling -reloadData How to keep UITableView contentoffset after calling -reloadData ios ios

How to keep UITableView contentoffset after calling -reloadData


I was having trouble with this because I mess with cell sizing in my cellForRowAtIndexPath method. I noticed that the sizing information was off after doing reloadData, so I realized I needed to force it to layout immediately before setting the content offset back.

CGPoint offset = tableView.contentOffset;[tableView.messageTable reloadData];[tableView layoutIfNeeded]; // Force layout so things are updated before resetting the contentOffset.[tableView setContentOffset:offset];


Calling reloadData on the tableView does not change the content offset. However, if you are using UITableViewAutomaticDimension which was introduced in iOS 8, you could have an issue.

While using UITableViewAutomaticDimension, one needs to write the delegate method tableView: estimatedHeightForRowAtIndexPath: and return UITableViewAutomaticDimension along with tableView: heightForRowAtIndexPath: which also returns the same.

For me, I had issues in iOS 8 while using this. It was because the method estimatedHeightForRowAtIndexPath: method was returning inaccurate values even though I was using UITableViewAutomaticDimension. It was problem with iOS 8 as there was no issue with iOS 9 devices.

I solved this problem by using a dictionary to store the value of the cell's height and returning it. This is what I did.

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{    NSNumber *key = @(indexPath.row);    NSNumber *height = @(cell.frame.size.height);    [self.cellHeightsDictionary setObject:height forKey:key];}- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{    NSNumber *key = @(indexPath.row);    NSNumber *height = [self.cellHeightsDictionary objectForKey:key];    if (height)    {        return height.doubleValue;    }    return UITableViewAutomaticDimension;}- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{    return UITableViewAutomaticDimension;}

The check for whether height exists is for the first time page loads.


Swift 5 variant of @Skywalker answer:

private var heightDictionary: [IndexPath: CGFloat] = [:]public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {    heightDictionary[indexPath] = cell.frame.size.height}public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {    let height = heightDictionary[indexPath]    return height ?? UITableView.automaticDimension}

Another solution (fetched from MessageKit):

This method should be called instead of reloadData. This can fit for specific cases.

extension UITableView {    public func reloadDataAndKeepOffset() {        // stop scrolling        setContentOffset(contentOffset, animated: false)                    // calculate the offset and reloadData        let beforeContentSize = contentSize        reloadData()        layoutIfNeeded()        let afterContentSize = contentSize                    // reset the contentOffset after data is updated        let newOffset = CGPoint(            x: contentOffset.x + (afterContentSize.width - beforeContentSize.width),            y: contentOffset.y + (afterContentSize.height - beforeContentSize.height))        setContentOffset(newOffset, animated: false)    }}