Swift
UITableView
iOS Development
Programming Tutorial
Mobile App Development

UITableView example for Swift

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

UITableView is the most commonly used component in iOS for displaying scrollable lists of data. It requires a data source (providing cells and section info) and optionally a delegate (handling selection and layout). This article shows a complete, working example of a UITableView in Swift — from basic setup through custom cells, sections, swipe actions, and the modern diffable data source API.

Basic UITableView Setup

swift
1import UIKit
2
3class FruitListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
4
5    let tableView = UITableView()
6    let fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
7
8    override func viewDidLoad() {
9        super.viewDidLoad()
10        title = "Fruits"
11
12        tableView.dataSource = self
13        tableView.delegate = self
14        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
15        tableView.frame = view.bounds
16        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
17        view.addSubview(tableView)
18    }
19
20    // MARK: - UITableViewDataSource
21
22    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
23        return fruits.count
24    }
25
26    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
27        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
28        cell.textLabel?.text = fruits[indexPath.row]
29        cell.accessoryType = .disclosureIndicator
30        return cell
31    }
32
33    // MARK: - UITableViewDelegate
34
35    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
36        tableView.deselectRow(at: indexPath, animated: true)
37        print("Selected: \(fruits[indexPath.row])")
38    }
39}

This is the minimum setup: register a cell, implement two data source methods, and handle selection.

Custom UITableViewCell

swift
1class FruitCell: UITableViewCell {
2    static let reuseIdentifier = "FruitCell"
3
4    let fruitImageView = UIImageView()
5    let nameLabel = UILabel()
6    let detailLabel = UILabel()
7
8    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
9        super.init(style: style, reuseIdentifier: reuseIdentifier)
10        setupUI()
11    }
12
13    required init?(coder: NSCoder) {
14        fatalError("init(coder:) has not been implemented")
15    }
16
17    private func setupUI() {
18        fruitImageView.translatesAutoresizingMaskIntoConstraints = false
19        fruitImageView.contentMode = .scaleAspectFill
20        fruitImageView.layer.cornerRadius = 20
21        fruitImageView.clipsToBounds = true
22
23        nameLabel.translatesAutoresizingMaskIntoConstraints = false
24        nameLabel.font = .systemFont(ofSize: 17, weight: .semibold)
25
26        detailLabel.translatesAutoresizingMaskIntoConstraints = false
27        detailLabel.font = .systemFont(ofSize: 14)
28        detailLabel.textColor = .secondaryLabel
29
30        contentView.addSubview(fruitImageView)
31        contentView.addSubview(nameLabel)
32        contentView.addSubview(detailLabel)
33
34        NSLayoutConstraint.activate([
35            fruitImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
36            fruitImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
37            fruitImageView.widthAnchor.constraint(equalToConstant: 40),
38            fruitImageView.heightAnchor.constraint(equalToConstant: 40),
39
40            nameLabel.leadingAnchor.constraint(equalTo: fruitImageView.trailingAnchor, constant: 12),
41            nameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
42            nameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
43
44            detailLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor),
45            detailLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 2),
46            detailLabel.trailingAnchor.constraint(equalTo: nameLabel.trailingAnchor),
47            detailLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
48        ])
49    }
50
51    func configure(name: String, detail: String) {
52        nameLabel.text = name
53        detailLabel.text = detail
54        fruitImageView.image = UIImage(systemName: "leaf.fill")
55    }
56}

Register and use in the view controller:

swift
1tableView.register(FruitCell.self, forCellReuseIdentifier: FruitCell.reuseIdentifier)
2
3func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
4    let cell = tableView.dequeueReusableCell(withIdentifier: FruitCell.reuseIdentifier, for: indexPath) as! FruitCell
5    cell.configure(name: fruits[indexPath.row], detail: "Fresh fruit")
6    return cell
7}

Sections

swift
1let sections = ["Fruits", "Vegetables"]
2let data = [
3    ["Apple", "Banana", "Cherry"],
4    ["Carrot", "Broccoli", "Spinach"]
5]
6
7func numberOfSections(in tableView: UITableView) -> Int {
8    return sections.count
9}
10
11func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
12    return sections[section]
13}
14
15func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
16    return data[section].count
17}
18
19func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
20    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
21    cell.textLabel?.text = data[indexPath.section][indexPath.row]
22    return cell
23}

Swipe Actions

swift
1func tableView(_ tableView: UITableView,
2               trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
3    let delete = UIContextualAction(style: .destructive, title: "Delete") { _, _, completion in
4        self.fruits.remove(at: indexPath.row)
5        tableView.deleteRows(at: [indexPath], with: .automatic)
6        completion(true)
7    }
8
9    let edit = UIContextualAction(style: .normal, title: "Edit") { _, _, completion in
10        print("Edit \(self.fruits[indexPath.row])")
11        completion(true)
12    }
13    edit.backgroundColor = .systemBlue
14
15    return UISwipeActionsConfiguration(actions: [delete, edit])
16}

Pull to Refresh

swift
1override func viewDidLoad() {
2    super.viewDidLoad()
3
4    let refreshControl = UIRefreshControl()
5    refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
6    tableView.refreshControl = refreshControl
7}
8
9@objc func refresh() {
10    // Reload data from server...
11    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
12        self.tableView.refreshControl?.endRefreshing()
13        self.tableView.reloadData()
14    }
15}

Common Pitfalls

  • Forgetting to register the cell: Calling dequeueReusableCell(withIdentifier:for:) without registering the cell class or nib first causes a crash. Always call tableView.register(CellClass.self, forCellReuseIdentifier:) in viewDidLoad.
  • Adding subviews to cell instead of cell.contentView: UI elements must be added to contentView, not the cell directly. Adding to the cell causes layout issues with editing mode, swipe actions, and accessory views.
  • Not calling dequeueReusableCell: Creating a new cell every time instead of dequeuing wastes memory and causes scroll performance issues. Always dequeue and configure the returned cell.
  • Forgetting reloadData after data changes: Modifying the data array without calling tableView.reloadData() (or insertRows/deleteRows) leaves the table view out of sync, causing crashes on scroll.
  • Cell reuse not resetting state: Dequeued cells retain state from their previous use. If a cell has an image and the new data has no image, the old image persists. Always reset all UI elements in cellForRowAt or override prepareForReuse().

Summary

  • Implement UITableViewDataSource for numberOfRowsInSection and cellForRowAt (minimum required)
  • Implement UITableViewDelegate for didSelectRowAt and layout customization
  • Always register cell classes before dequeuing — use tableView.register()
  • Create custom UITableViewCell subclasses for complex layouts with Auto Layout
  • Add subviews to contentView, not the cell directly
  • Use swipe actions, pull-to-refresh, and sections to build feature-rich table views

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.