Fix a table where fast-scrolled rows briefly show another row's image
A feed table loads each row's avatar asynchronously. When you scroll fast, rows briefly show another row's image before correcting itself. Diagnose the cause and fix it so a cell never shows an image meant for a different row.
Constraints:
- Keep cell reuse — do not disable
dequeueReusableCell. - Cancel or ignore a stale load; do not just call
reloadDataon every completion.
func tableView(_ tv: UITableView, cellForRowAt ip: IndexPath) -> UITableViewCell {
let cell = tv.dequeueReusableCell(withIdentifier: "Cell", for: ip)
let url = items[ip.row].avatarURL
ImageLoader.load(url) { image in
cell.imageView?.image = image // lands on whatever row the cell now shows
}
return cell
}
Find and fix the bug.
The completion captures the reused cell, recycled to another row before the image arrives, so it paints the wrong avatar. Reset the image in prepareForReuse and, on completion, confirm the cell still maps to that URL, else drop the stale load.
- ✗Blaming
IndexPathinstability instead of cell recycling - ✗Fixing it by disabling reuse instead of guarding the completion
- ✗Forgetting to reset the image before the async load starts
- →How does a per-cell load token let you ignore an outdated completion?
- →Why does resetting in
prepareForReusestill leave a flash without cancellation?
The cell is recycled before the image arrives, so the completion writes the avatar onto a cell that now shows a different row. Tie the completion to the requested URL and reset the image before loading:
final class AvatarCell: UITableViewCell {
private var loadingURL: URL?
override func prepareForReuse() {
super.prepareForReuse()
imageView?.image = nil // clear stale state
loadingURL = nil
}
func configure(_ url: URL) {
imageView?.image = nil
loadingURL = url
ImageLoader.load(url) { [weak self] image in
guard let self, self.loadingURL == url else { return } // still the same row?
self.imageView?.image = image
}
}
}
loadingURL acts as a token: a stale completion sees the mismatch and bails, while prepareForReuse clears the cell up front, so a foreign image has no way to appear.