Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jerky Scrolling After Updating UITableViewCell in place with UITableViewAutomaticDimension

I am building an app that has a feed view for user-submitted posts. This view has a UITableView with a custom UITableViewCell implementation. Inside this cell, I have another UITableView for displaying comments. The gist is something like this:

Feed TableView   PostCell     Comments (TableView)       CommentCell   PostCell     Comments (TableView)       CommentCell       CommentCell       CommentCell       CommentCell       CommentCell 

The initial feed will download with 3 comments for previewing, but if there are more comments, or if the user adds or deletes a comment, I want to update the PostCell in place inside of the feed table view by adding or removing CommentCells to the comments table inside of the PostCell. I am currently using the following helper to accomplish that:

// (PostCell.swift) Handle showing/hiding comments func animateAddOrDeleteComments(startRow: Int, endRow: Int, operation: CellOperation) {   let table = self.superview?.superview as UITableView    // "table" is outer feed table   // self is the PostCell that is updating it's comments   // self.comments is UITableView for displaying comments inside of the PostCell   table.beginUpdates()   self.comments.beginUpdates()    // This function handles inserting/removing/reloading a range of comments   // so we build out an array of index paths for each row that needs updating   var indexPaths = [NSIndexPath]()   for var index = startRow; index <= endRow; index++ {     indexPaths.append(NSIndexPath(forRow: index, inSection: 0))   }    switch operation {   case .INSERT:     self.comments.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)   case .DELETE:     self.comments.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)   case .RELOAD:     self.comments.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)   }    self.comments.endUpdates()   table.endUpdates()    // trigger a call to updateConstraints so that we can update the height constraint    // of the comments table to fit all of the comments   self.setNeedsUpdateConstraints() }  override func updateConstraints() {   super.updateConstraints()   self.commentsHeight.constant = self.comments.sizeThatFits(UILayoutFittingCompressedSize).height } 

This accomplishes the update just fine. The post is updated in place with comments added or removed inside of the PostCell as expected. I am using auto sizing PostCells in the feed table. The comments table of the PostCell expands to show all of the comments, but the animation is a bit jerky and the table sort of scrolls up and down a dozen pixels or so while the cell update animation takes place.

The jumping during resizing is a bit annoying, but my main issue comes afterwards. Now if I scroll down in the feed, the scrolling is smooth as before, but if I scroll up above the cell I just resized after adding comments, the feed will jump backwards a few times before it reaches the top of the feed. I setup iOS8 auto sizing cells for the Feed like this:

// (FeedController.swift) // tableView is the feed table containing PostCells self.tableView.rowHeight = UITableViewAutomaticDimension self.tableView.estimatedRowHeight = 560 

If I remove the estimatedRowHeight, the table just scrolls to the top anytime a cell height changes. I'm feeling pretty stuck on this now and as a new iOS developer, could use any tips you might have.

like image 603
Bryan Alger Avatar asked Jan 17 '15 05:01

Bryan Alger


2 Answers

Here is the best solution I found to solve this kind of problem (scrolling problem + reloadRows + iOS 8 UITableViewAutomaticDimension);

It consists by keeping every heights in a dictionary and updating them (in the dictionary) as the tableView will display the cell.

You will then return the saved height in - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath method.

You should implement something like this :

Objective-C

- (void)viewDidLoad {     [super viewDidLoad];      self.heightAtIndexPath = [NSMutableDictionary new];     self.tableView.rowHeight = UITableViewAutomaticDimension; }  - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {     NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath];     if(height) {         return height.floatValue;     } else {         return UITableViewAutomaticDimension;     } }  - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {     NSNumber *height = @(cell.frame.size.height);     [self.heightAtIndexPath setObject:height forKey:indexPath]; } 

Swift 3

@IBOutlet var tableView : UITableView? var heightAtIndexPath = NSMutableDictionary()  override func viewDidLoad() {     super.viewDidLoad()      tableView?.rowHeight = UITableViewAutomaticDimension }  func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {     if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {         return CGFloat(height.floatValue)     } else {         return UITableViewAutomaticDimension     } }  func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {     let height = NSNumber(value: Float(cell.frame.size.height))     heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying) } 
like image 165
dosdos Avatar answered Sep 19 '22 17:09

dosdos


We had the same problem. It comes from a bad estimation of the cell height that causes the SDK to force a bad height which will cause the jumping of cells when scrolling back up. Depending on how you built your cell, the best way to fix this is to implement the UITableViewDelegate method - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath

As long as your estimation is pretty close to the real value of the cell height, this will almost cancel the jumping and jerkiness. Here's how we implemented it, you'll get the logic:

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {     // This method will get your cell identifier based on your data     NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];      if ([cellType isEqualToString:kFirstCellIdentifier])         return kFirstCellHeight;     else if ([cellType isEqualToString:kSecondCellIdentifier])         return kSecondCellHeight;     else if ([cellType isEqualToString:kThirdCellIdentifier])         return kThirdCellHeight;     else {         return UITableViewAutomaticDimension;     } } 

Added Swift 2 support

func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {     // This method will get your cell identifier based on your data     let cellType = reuseIdentifierForIndexPath(indexPath)      if cellType == kFirstCellIdentifier          return kFirstCellHeight     else if cellType == kSecondCellIdentifier         return kSecondCellHeight     else if cellType == kThirdCellIdentifier         return kThirdCellHeight     else         return UITableViewAutomaticDimension   } 
like image 31
Gabriel Cartier Avatar answered Sep 20 '22 17:09

Gabriel Cartier