Andrew
Andrew

Reputation: 7883

UITableView with dynamic cell heights jumping when scrolling up after reloading cell

I have a table view with the potential for each cell to have its own height, thus isn't suitable to use rowHeight. Instead, right now I'm using let indexSet = NSIndexSet(index: 10), and self.tableView.estimatedRowHeight = 75. This means that it calls the sizeThatFits function on the cell, to determine its' height. This all works well.

The problem is then when you reload a cell that's onscreen. Scrolling down to show cell 10, for example, then reloading cell 10, works fine. But when you begin to scroll back up, past the cells you've already seen, it reverts to the estimatedRowHeight for each cell, completely disregarding sizeThatFits, and so jumps around as you scroll. It'd be impossible for me to give an accurate or 'good enough' estimatedRowHeight so that this jumping wouldn't be noticeable, as my cells will be able to display either a line of text, or a full image - a big difference in size.

I've shown this effect here:

https://vid.me/edgW

I've made many different attempts at this, using a mixture of heightForRowAtIndexPath, estimatedHeightForRowAtIndexPath.. etc.. I've tried various pieces of advice on StackOverflow. Nothing seems to work.

I've attached a very simple sample project, where you can try it for yourself:

https://www.dropbox.com/s/8f1rvkx9k23q6c1/tableviewtest.zip?dl=0

  1. Run the project.
  2. Scroll until cell 10 is in view.
  3. Wait up to 5 seconds for the cell to reload (it becomes purple).
  4. Scroll up.

Worth noting - this does not happen if the cell is not in view when it's reloaded. If it's either above or below the current scroll point, everything works as expected.

Upvotes: 15

Views: 13639

Answers (5)

SM Abu Taher Asif
SM Abu Taher Asif

Reputation: 2331

In iOS 13.5.1:

I have a tableView which contains 4 types of cells , all off them having different and dynamic heights. I have followed the accepted solution here, but to solve jumping effect I need to add more when reloading table view:

From accepted answer I have added below code: Declare this variable

var allCellHeights = [IndexPath : CGFloat]()

Then add 2 methods:

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

Now the extra code I have to add when reloading tableview:

let contentOffset = self.tableView.contentOffset
self.tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(contentOffset, animated: false)

also check this answer : reloadData() of UITableView with Dynamic cell heights causes jumpy scrolling

Upvotes: 5

I had this issue too and used a solution from SO which I am unable to find right now. If I do, I will add the link. Here's the solution:

The issue is with table view not having the right estimate for height of rows. To fix it, cache the height initially in willDisplayCell and next time use that height.

Code

In viewDidLoad:

heightAtIndexPath = [NSMutableDictionary new];


- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    NSNumber *height = @(cell.frame.size.height);
    [heightAtIndexPath setObject:height forKey:indexPath];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return UITableViewAutomaticDimension;
}

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
    if([heightAtIndexPath objectForKey:indexPath]) {
        return [[heightAtIndexPath objectForKey:indexPath] floatValue];
    } else {
        return UITableViewAutomaticDimension;
    }
}

Upvotes: 14

zwaldowski
zwaldowski

Reputation: 1035

This behavior appears to be a bug, if for no other reason than it's no longer reproducible on iOS 9. I'm sure that's not much consolation.

The issue primarily derives from having an inaccurate estimate, like @NickCatib said. The best you can do on iOS 8 is to improve the estimation. A technique many have recommended is to cache heights in willDisplayCell and use them on subsequent calls to estimatedRowHeightAtIndexPath.

You might be able to mitigate the behavior by not doing anything to get UITableView to discard its caches, like by modifying the content in a cell directly using cellForRowAtIndexPath rather than using reloading if it's onscreen. However, that won't help if you actually need to change the height of the cell.

I'm afraid to say the bug can't be easily be fixed within a table view, as you don't have control over the layout. The bug can be more easily worked around in a subclass UICollectionViewFlowLayout by changing the contentOffsetAdjustment during invalidation, although that might not be terribly easy.

Upvotes: 19

Umair_UAS
Umair_UAS

Reputation: 123

I was facing the same problem, my table works fine until the tableview reloads. So i found a solution, use only rowHeight not estimated height. Also if you have different height. So please provide the complete code so i will provide a solution. I have a cell which like instagram page. I am passing calculated height in heightforrow method which work fine. But estimated height not works fine in that situation. If you use below code, it works fine. Please try.

self.tableView.rowHeight = 75 //it will be your dynamic height
//self.tableView.estimatedRowHeight = 75

If you confuse to calculate the height for row for each cell, just post the sample i will provide the solution. If i can Thanks

Upvotes: -2

Miknash
Miknash

Reputation: 7948

Uh... this is kind of hard issue to deal with.

Let's take a look at facebook. They had this same issue with their timeline, and they ended up doing it in some kind of web view.

I had a similar issue with some kind of timeline, used automatic row height and had that issue. First thing to resolve it was to set estimatedHeight as close as possible to average cell height. That is pretty hard to deal with since you may have text (height 50) or images + text ( height 1500). Next thing to do with improving this was implementing estimatedHeight forIndexPath which basicly return different estimated height for different indexPaths.

After that there were a lot of other solutions but this was as closest as it can with variable heights (huge differences).

Upvotes: 0

Related Questions