Why do animations not work when deselecting a `UITableViewCell` in a `UITableView` asynchronously?

Below is a simple example. If you tap and release the first cell, it deselects with the animation as you would expect.

If you tap and release the second cell, you may not notice anything happen at all. You may even need to hold the cell to see the selection, and when you release it reverts back to the unselected state immediately.

This is obviously a contrived example but it shows the essence of the issue. If I wanted to delay the deselection until something had happened I would face the same issue.

Why does this happen and is there any way around it?

import UIKit

class ViewController: UITableViewController {

	override func viewDidLoad() {
		super.viewDidLoad()
		tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
	}
	
	override func numberOfSections(in tableView: UITableView) -> Int {
		return 1
	}

	override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
		return 2
	}
	
	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
		cell.textLabel?.text = indexPath.row == 0 ? "Regular deselection" : "Async deselection"
		return cell
	}
	
	override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
		if indexPath.row == 0 {
			tableView.deselectRow(at: indexPath, animated: true)
		} else {
			DispatchQueue.main.async {
				tableView.deselectRow(at: indexPath, animated: true)
			}
		}
	}
}

Replies

Effectively, with the dispatch, there is no delay between tap and deselection. Probably, the dispatch causes the action to execute immediately.

So, I added a short sleep before deselect and get the same visual effect.

			DispatchQueue.main.async {
        sleep(200_000)
				tableView.deselectRow(at: indexPath, animated: true)
			}
  • Even if it is executed immediately it should be no different than executing it in the didDeselect method as demonstrated in the example. Surely, having the deselect in the async block would execute it at least one frame after it was scheduled?

  • The detailed explanation you got in         https://stackoverflow.com/questions/69160053/why-do-animations-not-work-when-deselecting-a-uitableviewcell-in-a-uitableview-a     seems relevant: it is a question of how update events are queued in the system. And dispatch changes the way it is handled.

  • Yes, that could be the situation. In which case it's quite disappointing that I will need to roll my own animations.