0

I am trying to add some rows in my table view. when inserting rows are above the rows which are on the screen, the table view jumps up. I want my table view to stay in the position it is already in when I insert rows above. Keep in mind: tableView jump to indexPath that it was showing but after adding rows above, bottom rows indexPaths changes and the new n'th indexPath is something else.

Ali Samaiee
  • 301
  • 6
  • 14

1 Answers1

1

This is unfortunately not as easy task as one would think. Table view jumps when you add a cell on top because the offset is persisted and cells updated. So in a sense it is not the table view that jumps, cells jump since you added a new one on top which makes sense. What you want to do is for your table view to jump with the added cell.

I hope you have fixed or computed row heights because with automatic dimensions things can complicate quite a bit. It is important to have the same estimated height as actual height for row. In my case I just used:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 72.0
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return 72.0
}

Then for testing purposes I add a new cell on top whenever any of the cells is pressed:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    var offset = tableView.contentOffset.y
    cellCount += 1
    tableView.reloadData()
    let paths = [IndexPath(row: 0, section: 0)]
    paths.forEach { path in
        offset += self.tableView(tableView, heightForRowAt: path)
    }
    DispatchQueue.main.async {
        tableView.setContentOffset(CGPoint(x: 0.0, y: offset), animated: false)
    }
}

So I save what the current offset of the table view is. Then I modify the data source (My data source is just showing number of cells). Then simply reload the table view.

I grab all the index paths that have been added and I modify the offset by adding the expected height of every added cell.

At the end I apply the new content offset. And it is important to do that in the next run loop which is easies done by dispatching it asynchronously on main queue.

As for automatic dimensions.

I would not go there but it should be important to have size cache.

private var sizeCache: [IndexPath: CGFloat] = [IndexPath: CGFloat]()

Then you need to fill the size cache when cell disappears:

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    sizeCache[indexPath] = cell.frame.size.height
}

And change the estimated height:

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return sizeCache[indexPath] ?? 50.0
}

Also when modifying your offset you need to use estimated height:

paths.forEach { path in
    offset += self.tableView(tableView, estimatedHeightForRowAt: path)
}

This worked for my case but automatic dimensions are sometimes tricky so good luck with them.

Matic Oblak
  • 16,318
  • 3
  • 24
  • 43