Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scrollToRowAtIndexPath:atScrollPosition causing table view to "jump"

My app has chat functionality and I'm feeding in new messages like this:

[self.tableView beginUpdates];
[messages addObject:msg];
[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:messages.count - 1 inSection:1]] withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:messages.count - 1 inSection:1] atScrollPosition:UITableViewScrollPositionBottom animated:YES];

However, my table view "jumps" weirdly when I'm adding a new message (either sending and receiving, result is the same in both):

enter image description here

Why am I getting this weird "jump"?

like image 511
Can Poyrazoğlu Avatar asked Feb 09 '16 11:02

Can Poyrazoğlu


2 Answers

OK, I figured it out. As you say, the problem has to do with auto-sizing cells. I used two tricks to make things work (my code is in Swift, but it should be easy to translate back to ObjC):

1) Wait for the table animation to finish before taking further action. This can be done by enclosing the code that updates the table within a block between CATransaction.begin() and CATransaction.commit(). I set the completion block on CATransaction -- that code will run after the animation is finished.

2) Force the table view to render the cell before scrolling to the bottom. I do it by increasing the table's contentOffset by a small amount. That causes the newly inserted cell to get dequeued, and its height gets calculated. Once that scroll is done (I wait for it to finish using the method (1) above), I finally call tableView.scrollToRowAtIndexPath.

Here's the code:

override func viewDidLoad() {
    super.viewDidLoad()

    // Use auto-sizing for rows        
    tableView.estimatedRowHeight = 40
    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.dataSource = self
}

func chatManager(chatManager: ChatManager, didAddMessage message: ChatMessage) {
    messages.append(message)

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

    CATransaction.begin()
    CATransaction.setCompletionBlock({ () -> Void in
        // This block runs after the animations between CATransaction.begin
        // and CATransaction.commit are finished.
        self.scrollToLastMessage()
    })

    tableView.beginUpdates()
    tableView.insertRowsAtIndexPaths([indexPathToInsert], withRowAnimation: .Bottom)
    tableView.endUpdates()

    CATransaction.commit()
}

func scrollToLastMessage() {
    let bottomRow = tableView.numberOfRowsInSection(0) - 1

    let bottomMessageIndex = NSIndexPath(forRow: bottomRow, inSection: 0)

    guard messages.count > 0
        else { return }

    CATransaction.begin()
    CATransaction.setCompletionBlock({ () -> Void in

        // Now we can scroll to the last row!
        self.tableView.scrollToRowAtIndexPath(bottomMessageIndex, atScrollPosition: .Bottom, animated: true)
    })

    // scroll down by 1 point: this causes the newly added cell to be dequeued and rendered.
    let contentOffset = tableView.contentOffset.y
    let newContentOffset = CGPointMake(0, contentOffset + 1)
    tableView.setContentOffset(newContentOffset, animated: true)

    CATransaction.commit()
}
like image 153
TotoroTotoro Avatar answered Nov 07 '22 03:11

TotoroTotoro


Change UITableViewRowAnimationBottom to UITableViewRowAnimationNone and try

like image 1
Arun Kumar P Avatar answered Nov 07 '22 03:11

Arun Kumar P