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):
Why am I getting this weird "jump"?
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()
}
Change UITableViewRowAnimationBottom
to UITableViewRowAnimationNone
and try
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With