How to keep UITableView contentoffset after calling -reloadData

IosUitableviewReloaddataContentoffset

Ios Problem Overview


CGPoint offset = [_table contentOffset];
[_table reloadData];
[_table setContentOffset:offset animated:NO];    //unuseful

//    __block UITableView *tableBlock = _table;
//    [self performBlock:^(id sender) {
//        [tableBlock setContentOffset:offset];
//    } afterDelay:2];

I know don't know of any delegate method which gets called after reloadData. And using afterDelay:2 which is kind of a hack may be too short or too long, so how can I implement it?

Ios Solutions


Solution 1 - Ios

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];

Solution 2 - Ios

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.

Solution 3 - Ios

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)
    }
}

Solution 4 - Ios

In my case uncheck row height automatic and estimate automatic problem solved

fix reload problem

Solution 5 - Ios

By default, reloadData keeps the contentOffset. However, it could be updated if you do have inaccurate estimatedRowHeight values.

Solution 6 - Ios

If you implement estimatedHeightForRowAtIndexPath method and your estimate is not right, you will possible get into this situation.

To solve this, you can return a large height that bigger than every cell height in your tableView, like this:

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { 
    return 800.f; // if 800 is bigger than every possible value of your cell height.
}

Solution 7 - Ios

I was recently working with reloadData -- reloadData doesn't change the contentOffset or scroll the table view. It actually stays the same if the offset is less than the new amount of data.

Solution 8 - Ios

If you insert data at the beginning of your dataSource array, you need to change contentOffset like this: Swift 3+

func prepareReloadData() {
    let previousContentHeight = tableView.contentSize.height
    let previousContentOffset = tableView.contentOffset.y
    tableView.reloadData()
    let currentContentOffset = tableView.contentSize.height - previousContentHeight + previousContentOffset
    tableView.contentOffset = CGPoint(x: 0, y: currentContentOffset)
}

Solution 9 - Ios

I had the same issue however none of answers suggested here worked. Here's how i solved it. Subclass UITableView and override layoutSubviews method like this:

override func layoutSubviews() {
    let offset = contentOffset
    super.layoutSubviews()
    contentOffset = offset
}

Solution 10 - Ios

@Skywalker's answer showed best workaround for estimated height of cells problem. But sometimes problem lyes in a different place.
Sometimes the problem lyes in contentInsets of table view. If you make reload data while tableView is not visible on the screen you can face with wrong offset after the table view appears on the screen.
It happens because UIViewController can control insets if his scrollView when the scrollView is appearing to allow lying of scrollView below transparent navigationBar and statusBar.
I've faced with this behaviour in iOS 9.1

Solution 11 - Ios

Matt's answer above made me realize that it has to be an issue with the estimatedRowHeight.

So as several pointed out reloadData should not modify the contentOffset so when you set your rowHeight = UITableView.automaticDimension just to be sure to set a correct estimatedRowHeight.

If the estimatedRowHeight is shorter than then UITableView estimated it will try to fix the height and the scrolling behavior would appear but only if the cell with the height problem is visible. In other words, be sure your estimatedRowHeight is set correctly.

I hope this could help someone else.

Solution 12 - Ios

This is working 100%

change the tableView.reloadData() 

into

tableView.reloadRows(at: tableView!.indexPathsForVisibleRows!, with: .none)

Solution 13 - Ios

For it works fine

[tView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
             atScrollPosition:UITableViewScrollPositionTop
                     animated:NO];

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionavincrossView Question on Stackoverflow
Solution 1 - IosMatt KoalaView Answer on Stackoverflow
Solution 2 - IosSkywalkerView Answer on Stackoverflow
Solution 3 - IosNike KovView Answer on Stackoverflow
Solution 4 - IossaranpolView Answer on Stackoverflow
Solution 5 - IosCyrilView Answer on Stackoverflow
Solution 6 - Iosbingxin xueView Answer on Stackoverflow
Solution 7 - IosRoyView Answer on Stackoverflow
Solution 8 - IosBohdan SavychView Answer on Stackoverflow
Solution 9 - IosMikhail VasilevView Answer on Stackoverflow
Solution 10 - IosAndrew RomanovView Answer on Stackoverflow
Solution 11 - IosVictor SiglerView Answer on Stackoverflow
Solution 12 - IosRandolf Dini-ayView Answer on Stackoverflow
Solution 13 - IosJohnView Answer on Stackoverflow