Swift
UITableView
iOS Development
Programming Tutorial
Insert Cell

How to insert new cell into UITableView in Swift

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Inserting a row into a UITableView is straightforward once you remember the core rule: update your data model first, then tell the table view which row was inserted. Most crashes in this area happen because the array and the visible rows get out of sync.

Set Up a Table with a Backing Array

The table view does not store your business data for you. A simple array is usually the source of truth for row content.

swift
1import UIKit
2
3final class ItemsViewController: UIViewController, UITableViewDataSource {
4    private let tableView = UITableView(frame: .zero, style: .plain)
5    private var items = ["Milk", "Bread", "Coffee"]
6
7    override func viewDidLoad() {
8        super.viewDidLoad()
9
10        view.backgroundColor = .systemBackground
11        tableView.frame = view.bounds
12        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
13        tableView.dataSource = self
14        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
15        view.addSubview(tableView)
16
17        navigationItem.rightBarButtonItem = UIBarButtonItem(
18            barButtonSystemItem: .add,
19            target: self,
20            action: #selector(addItem)
21        )
22    }
23
24    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
25        items.count
26    }
27
28    func tableView(
29        _ tableView: UITableView,
30        cellForRowAt indexPath: IndexPath
31    ) -> UITableViewCell {
32        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
33        cell.textLabel?.text = items[indexPath.row]
34        return cell
35    }
36}

This controller already displays rows. The only missing piece is the insertion logic.

Insert a New Row Correctly

When the user taps the add button, append or insert into the array first. Then call insertRows(at:with:) using the same index.

swift
1extension ItemsViewController {
2    @objc private func addItem() {
3        let newItem = "Item \(items.count + 1)"
4        let insertIndex = 0
5
6        items.insert(newItem, at: insertIndex)
7
8        let indexPath = IndexPath(row: insertIndex, section: 0)
9        tableView.insertRows(at: [indexPath], with: .automatic)
10    }
11}

This example inserts at the top of the list. If you want to add to the end instead, use items.append(newItem) and set the row to items.count - 1 after the append.

UITableView animates the insertion and asks the data source for the new cell. Because the array already contains the item, cellForRowAt can return the correct content without inconsistency.

Use Batch Updates for Multiple Changes

If you insert several rows or combine insertions with deletions, wrap the changes in a batch update. That keeps the animation and index math consistent.

swift
1func insertThreeRows() {
2    let newItems = ["Tea", "Apples", "Cheese"]
3    let startIndex = items.count
4
5    items.append(contentsOf: newItems)
6
7    let indexPaths = (0..<newItems.count).map {
8        IndexPath(row: startIndex + $0, section: 0)
9    }
10
11    tableView.performBatchUpdates {
12        tableView.insertRows(at: indexPaths, with: .fade)
13    }
14}

Batch updates are especially useful when the visible result depends on multiple coordinated changes.

Common Pitfalls

The most frequent mistake is calling insertRows before updating the data array. The table view then believes there is an extra row, but the data source still reports the old count. That mismatch often triggers the classic "invalid number of rows" crash.

Another issue is computing the wrong index path. If you insert at the top, the index path must be row 0. If you append, the new row must be the last valid index after the array has changed.

Reloading the entire table with reloadData() also works, but it gives up the insertion animation and can be less efficient. Use it when the whole table truly changed, not as a substitute for correct row insertion logic.

Finally, make sure UI updates happen on the main thread. If data arrives from a network request or background queue, dispatch the insert back to DispatchQueue.main.async before touching UITableView.

Summary

  • Keep a backing array or other data source as the single source of truth.
  • Insert into the model first, then call insertRows(at:with:).
  • Use matching index paths so the table view and data source stay consistent.
  • Prefer batch updates when applying several row changes together.
  • Touch UITableView only on the main thread to avoid race conditions and crashes.

Course illustration
Course illustration

All Rights Reserved.