Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableViewController: Scrolling to bottom with dynamic row height starts animation at wrong position

I have a table view properly configured to have dynamic row heights based on Ray Wenderlich's guide found here:

I set up the constraints to have a clear line of constraints from the top to the bottom of the cell. I also set up content hugging and content compression resistance priorities and estimated row height.

This is the code I use to setup the table view:

func configureTableView() {
    // its called on viewDidLoad()
    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100.0
}

override func viewDidLoad() {
    super.viewDidLoad()

    configureTableView()

    for i in 1...20 {
        messages.append([
            "title": "foo \(i)",
            "message": "bla \(i)\nbla\nbla"
        ])
    }

    // this is because the actual row heights are not available until the next layout cycle or something like that
    dispatch_async(dispatch_get_main_queue(), {self.scrollToBottom(false)})
}

func scrollToBottom(animated:Bool) {
    let indexPath = NSIndexPath(forRow: self.messages.count-1, inSection: 0)

    self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: animated)
}

And this is how I add new rows:

@IBAction func addMore(sender:UIBarButtonItem) {
    let message = [
        "title": "haiooo",
        "message": "silver"]

    messages.append(message)

    let indexPath = NSIndexPath(forRow: messages.count-1, inSection: 0)

    tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Bottom)

    scrollToBottom(true)
}

The setup with the default rows are fine. It add the rows and scrolls to the bottom as expected.

But when I add new rows after that, the scrolling seems to start above the last cell. As I add more cells, the offset seems to increase.

Here is a gif showing it happening: Imgur

It's certainly related to the scroll animation (not the insertRow animation) because it scrolls properly when the animation is turned off.

Changing the estimatedRowHeight makes difference on the scrolling offset, but I couldn't find a value that fixed it.

I also tried delaying up the scroll using dispatch_async but it didn't change anything.

Do you guys have any ideas?

like image 548
Marcio Cruz Avatar asked Feb 07 '16 02:02

Marcio Cruz


1 Answers

Wow, that was a fun challenge. Thanks for posting the test project.

So it seems that after adding the new row there's something off with where the table view thinks it's scrolled to. Seems to me to be a bug in UIKit. So to work around that, I added some code to 'reset' the table view before applying the animation.

Here's what I ended up with:

@IBAction func addMore(sender:UIBarButtonItem) {
    let message = [
        "title": "haiooo",
        "message": "silver"]
    messages.append(message)

    tableView.reloadData()

    // To get the animation working as expected, we need to 'reset' the table
    // view's current offset. Otherwise it gets confused when it starts the animation.
    let oldLastCellIndexPath = NSIndexPath(forRow: messages.count-2, inSection: 0)
    self.tableView.scrollToRowAtIndexPath(oldLastCellIndexPath, atScrollPosition: .Bottom, animated: false)

    // Animate on the next pass through the runloop.
    dispatch_async(dispatch_get_main_queue(), {
        self.scrollToBottom(true)
    })
}

I couldn't get it to work with insertRowsAtIndexPaths(_:withRowAnimation:), but reloadData() worked fine. Then you need the same delay again before animating to the new last row.

like image 162
Dave Batton Avatar answered Sep 18 '22 16:09

Dave Batton