Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
182 views
in Technique[技术] by (71.8m points)

ios - Why does my UITableView "jump" when inserting or removing a row?

(Happy to accept an answer in Swift or Objective-C)

My table view has a few sections, and when a button is pressed, I want to insert a row at the end of section 0. Pressing the button again, I want delete that same row. My almost working code looks like this:

// model is an array of mutable arrays, one for each section

- (void)pressedAddRemove:(id)sender {
    self.adding = !self.adding;  // this is a BOOL property
    self.navigationItem.rightBarButtonItem.title = (self.adding)? @"Remove" : @"Add";

    // if adding, add an object to the end of section 0
    // tell the table view to insert at that index path

    [self.tableView beginUpdates];
    NSMutableArray *sectionArray = self.model[0];
    if (self.adding) {
        NSIndexPath *insertionPath = [NSIndexPath indexPathForRow:sectionArray.count inSection:0];
        [sectionArray addObject:@{}];
        [self.tableView insertRowsAtIndexPaths:@[insertionPath] withRowAnimation:UITableViewRowAnimationAutomatic];

    // if removing, remove the object from the end of section 0
    // tell the table view to remove at that index path

    } else {
        NSIndexPath *removalPath = [NSIndexPath indexPathForRow:sectionArray.count-1 inSection:0];
        [sectionArray removeObject:[sectionArray lastObject]];
        [self.tableView deleteRowsAtIndexPaths:@[removalPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    }
    [self.tableView endUpdates];
}

This behaves properly sometimes, but sometimes not, depending on where the table view is scrolled:

  • Section 0 at the very top, contentOffset.y == 0: Works great, the row is inserted and the stuff below section 0 animates downward
  • Section 0 invisible, because the table is scrolled past it: Works great, the visible content below the new row animates downward as if a row was inserted above it.
  • BUT: if the table view is scrolled a little, so that part of section 0 is visible: it works wrong. In a single frame, all of the content in the table view jumps up (content offset increases) Then, with animation, the new row gets inserted and the table view content scrolls back down (content offset decreases). Everything ends up where it should be, but the process looks super bad with that single frame "jump" at the start.

I can see this happen in slow-motion the simulator with "Debug->Toggle Slow Animations". The same problem occurs in reverse on the deletion.

I've found that the size of the jump in offset is related to the how far into section 0 the table is scrolled: the jump tiny when the offset is tiny. The jump gets bigger as the scrolling approaches half of section 0 total height (the problem is at it's worst here, jump == half the section height). Scrolling further, the jump gets smaller. When the table is scrolled so that only a tiny amount of section 0 is still visible, the jump is tiny.

Can you help me understand why this is and how to fix?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

On iOS 11, UITableView uses estimated row height as default.

It leads to unpredictable behaviors when inserting/reloading or deleting rows because the UITableView has a wrong content size most of the time:

To avoid too many layout calculations, the tableView asks heightForRow only for each cellForRow call and remembers it (in normal mode, the tableView asks heightForRow for all the indexPaths of the tableView). The rest of the cells has a height equal to the estimatedRowHeight value until their corresponding cellForRow is called .

// estimatedRowHeight mode
contentSize.height = numberOfRowsNotYetOnScreen * estimatedRowHeight + numberOfRowsDisplayedAtLeastOnce * heightOfRow

// normal mode
contentSize.height = heightOfRow * numberOfCells

I guess UIKit struggles to animate correctly the changes because of this trick.

One solution is to disable the estimatedRowHeight mode by setting estimatedRowHeight to 0 and implementing heightForRow for each of your cells.

Of course, if your cells have dynamic heights (with onerous layout calculations most of time so you used estimatedRowHeight for a good reason), you would have to find a way to reproduce the estimatedRowHeight optimization without compromising the contentSize of your tableView. Take a look at AsyncDisplayKit or UITableView-FDTemplateLayoutCell.

Another solution is to try to find a estimatedRowHeight which suits well. Since iOS 10, you can also try to use UITableView.automaticDimension. UIKit will find a value for you:

tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = UITableView.automaticDimension

On iOS 11, it's already the default value.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...