0

I'm building an app where rows containing messages are inserted at the end of a table

messages.append(message)
let indexPath:IndexPath = IndexPath(row:(messages.count - 1), section:0)
tableView.insertRows(at: [indexPath], with: .none)

I then scroll to the bottom of the table

DispatchQueue.main.async {
    let indexPath = IndexPath(
        row: self.numberOfRows(inSection:  self.numberOfSections - 1) - 1,
        section: self.numberOfSections - 1)
    self.scrollToRow(at: indexPath, at: .bottom, animated: true)
}

Like other messaging apps, I would like to modify this so that the autoscrolling only happens if you're already scrolled at the end of the table instead of every time a new message gets inserted.

I've tried several techniques like detecting if the last cell is full visible https://stackoverflow.com/a/9843146/784637, or detecting when scrolled to the bottom https://stackoverflow.com/a/39015301/784637.

However my issue is that because scrollToRow sets animated:true, if a new message comes in but the previous message which came a split second before is still being scrolled down to via scrollToRow, then the autoscrolling to newest message and subsequent messages doesn't occur - ex. the last cell won't be fully visible until the animation is complete, or detecting if you're scrolled to to the bottom will be false until the animation is complete.

Is there any way I can get around this without setting animated: false?

user784637
  • 13,012
  • 31
  • 83
  • 144

1 Answers1

0

What I would do is insert the row in a batch operation to make use of its completion handler, which serializes the UI update. Then I would check to see which rows are visible to the user and if the last couple of rows are, I think the user is close enough to the bottom of the conversation to force a down scroll.

let lastRow = IndexPath(row: messages.count - 1, section: 0)

DispatchQueue.main.async {

    self.tableView.performBatchUpdates({
        self.tableView.insertRows(at: [lastRow], with: .bottom)
    }, completion: { (finished) in

        guard finished,
            let visiblePaths = self.tableView.indexPathsForVisibleRows else {
                return
        }

        if visiblePaths.contains([0, messages.count - 2]) || visiblePaths.contains([0, messages.count - 1]) { // last 2 rows are visible

            DispatchQueue.main.async {
                self.tableView.scrollToRow(at: lastRow, at: .bottom, animated: true)
            }

        }

    })

}
MJ-12
  • 8,200
  • 2
  • 25
  • 33