UITableView
iOS development
Swift programming
row animation
completion handler

UITableView row animation duration and completion callback

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

UITableView gives you built-in animations for inserting, deleting, moving, and reloading rows, but it does not expose much direct control over the animation timing. In practice, the important question is usually not "what is the exact duration," but "how do I run code after the update animation has finished?"

What UIKit Actually Lets You Control

The row animation style, such as .fade, .automatic, or .right, is public API. The exact duration of those built-in table view animations is not something UIKit exposes as a documented knob you can set per update.

That leads to two practical conclusions:

  • Do not hardcode logic that depends on a specific built-in row-animation duration
  • Use an API with an explicit completion callback when you need to chain work after the visual update

On modern iOS, the cleanest approach is performBatchUpdates(_:completion:).

Using performBatchUpdates

performBatchUpdates lets you update both your data source and your table view inside one block, then receive a completion callback when the animation finishes.

swift
1import UIKit
2
3final class ItemsViewController: UITableViewController {
4    private var items = ["One", "Two", "Three"]
5
6    override func tableView(_ tableView: UITableView,
7                            numberOfRowsInSection section: Int) -> Int {
8        items.count
9    }
10
11    override func tableView(_ tableView: UITableView,
12                            cellForRowAt indexPath: IndexPath) -> UITableViewCell {
13        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
14            ?? UITableViewCell(style: .default, reuseIdentifier: "Cell")
15        cell.textLabel?.text = items[indexPath.row]
16        return cell
17    }
18
19    func removeFirstRow() {
20        guard !items.isEmpty else { return }
21
22        tableView.performBatchUpdates({
23            items.remove(at: 0)
24            tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
25        }, completion: { finished in
26            if finished {
27                print("Deletion animation completed")
28            }
29        })
30    }
31}

This is the most straightforward way to react after the row animation is done. The finished flag is useful when the update is interrupted or the view disappears during the transition.

What About Animation Duration

If you are using the built-in UITableView.RowAnimation styles, UIKit controls the timing. Apple does not provide a supported per-call duration parameter for insertRows, deleteRows, or reloadRows.

If you truly need custom timing, you usually have two alternatives:

  • Build a custom transition using UIView animation APIs and update visible cells manually
  • Reconsider whether a custom duration is necessary at all

For typical list editing, the platform-default timing feels most natural and keeps your UI consistent with the rest of iOS.

Supporting Older Patterns

Older codebases often use beginUpdates() and endUpdates(). Those methods still work for grouping changes, but they do not provide a completion closure directly. If you need a completion callback in that style of code, CATransaction is a common bridge.

swift
1import UIKit
2import QuartzCore
3
4func insertRowWithCompletion(tableView: UITableView,
5                             dataSource: inout [String],
6                             newValue: String,
7                             completion: @escaping () -> Void) {
8    CATransaction.begin()
9    CATransaction.setCompletionBlock(completion)
10
11    dataSource.append(newValue)
12    let indexPath = IndexPath(row: dataSource.count - 1, section: 0)
13
14    tableView.beginUpdates()
15    tableView.insertRows(at: [indexPath], with: .fade)
16    tableView.endUpdates()
17
18    CATransaction.commit()
19}

This pattern is useful when you cannot yet migrate the surrounding code to performBatchUpdates.

Keep Data Source and UI in Sync

No matter which update API you use, the order of operations matters. Your backing array must reflect the new row count by the time the table view asks for data. If you delete a row in the UI before updating the model, you can trigger the classic invalid-update crash.

For example, this is wrong:

  • Call deleteRows
  • Leave the data array unchanged
  • Let the table view query the old count

This is correct:

  • Update the data array inside the batch
  • Apply the matching table-view operation
  • Let UIKit animate the consistent before-and-after states

When You Need a Post-Animation Scroll or Focus Change

A common reason for needing completion is to scroll to the inserted row, focus a text field, or trigger another animation after the table view settles. Doing that work inside the batch update block is often too early. The completion closure is the safe place because layout and row transitions are already resolved.

That keeps follow-up UI work deterministic:

swift
1tableView.performBatchUpdates({
2    items.append("New item")
3    let indexPath = IndexPath(row: items.count - 1, section: 0)
4    tableView.insertRows(at: [indexPath], with: .bottom)
5}, completion: { _ in
6    let lastRow = IndexPath(row: self.items.count - 1, section: 0)
7    self.tableView.scrollToRow(at: lastRow, at: .bottom, animated: true)
8})

Common Pitfalls

The biggest mistake is assuming a fixed built-in duration such as 0.25 seconds and using DispatchQueue.main.asyncAfter to approximate completion. That is fragile and can break with different device conditions, reduced-motion settings, or future UIKit behavior.

Another pitfall is mutating the data source outside the update block or in the wrong order. Table view update crashes are usually model-sync bugs, not animation bugs.

Developers also sometimes use reloadData() when they only need row-level updates. reloadData() discards the fine-grained animation context and offers no row animation completion semantics of its own.

Finally, if you support older iOS code paths, do not forget that CATransaction completion is tied to Core Animation transactions. Keep the grouped changes consistent so the completion fires when you expect.

Summary

  • 'UITableView.RowAnimation lets you choose style, but not a documented per-call duration.'
  • Use performBatchUpdates(_:completion:) when you need a reliable completion callback.
  • In older code, CATransaction can wrap beginUpdates() and endUpdates() to detect completion.
  • Update the data source and the table view together to avoid invalid-update crashes.
  • Avoid guessing animation duration with timers; use completion-based coordination instead.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.