I have this UITableView in my iOS chat application, I’m using NSFetchedResultsController to fetch new messages from Coredata and show them to the screen. Insert a new message to the bottom of the uitableview, animate it to this message, and update cells when a message object changes.
lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .plain)
table.translatesAutoresizingMaskIntoConstraints = false
table.register(UITableViewCell.self, forCellReuseIdentifier: "MessageCell")
table.backgroundColor = .clear
table.separatorStyle = .none
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = UITableView.automaticDimension
table.keyboardDismissMode = .interactive
table.allowsSelection = false
if #available(iOS 15.0, *) {
table.sectionHeaderTopPadding = 0
}
return table
}()
var cellHeights = IndexPath: CGFloat
this is the UITableViewDelegate def:
extension ChatViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? UITableView.automaticDimension
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.viewModel.sectionsCount
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.viewModel.numberOfRowsInSection(section: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let messageViewModel = self.viewModel.getViewModel(for: message)
let cell = tableView.dequeueReusableCell(withIdentifier: messageViewModel.identifier, for: indexPath) as! TMMessageCell
cell.viewModel = messageViewModel
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
viewModel.headerViewForSection(section: section)
}
}
This is how I insert/update the table:
func controller(_ controller: NSFetchedResultsController<any NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
if type == .insert {
DispatchQueue.main.async {
self.tableView.beginUpdates()
self.tableView.insertRows(at: [newIndexPath!], with: .bottom)
self.tableView.endUpdates()
self.tableView.scrollToRow(at: newIndexPath!,
at: .bottom,
animated: true)
}
} else if type == .update {
DispatchQueue.main.async {
self.tableView.beginUpdates()
self.tableView.reloadRows(at: [indexPath!], with: .none)
self.tableView.endUpdates()
}
}
}
when I get messages it works fine(the insert and the scroll), but when I update the cell(right after the scroll) there is a strange scroll in the tableview. Any idea what can be the problem?
I must use the refresh because sometimes I need to change the uitableviewcell because of parameters that I get from the object, so getting the cell and updating it is not an option