Multiple UITableViews inside one UIViewController

This article is a cleaner pattern guide: instead of keeping every UITableViewDelegate and UITableViewDataSource method inside one view controller, move that logic into a reusable helper object. Once that is in place, wiring two or more tables into the same UIViewController becomes much lighter.

An iPhone screenshot showing two separate table views rendered in one screen

The goal is not a new table view feature. It is a cleaner way to stop one controller from owning every table callback itself.

This article focuses on a simple refactor. If one screen needs more than one UITableView, repeating delegate and data-source logic directly inside the controller gets noisy fast. A separate helper object can own the table behavior instead.

That pattern works best when each table is mostly presentational and the screen just needs different datasets wired into the same reusable table logic.

Main Idea Create one reusable table-data-source object, then give each table view its own instance and its own data.

The starting point is the standard UIKit approach: one outlet, then delegate and data-source methods on the controller.

The article first shows the common structure most UIKit projects begin with: the table view outlet is owned by the view controller, and the controller itself conforms to both UITableViewDelegate and UITableViewDataSource.

@IBOutlet weak var tableView: UITableView!

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // Return the row count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // Build a cell
    }
}

That is fine for one simple list. The problem starts when the controller has to manage two different table views, each with similar rendering logic but different data.

Move the table behavior into a standalone class so the controller stops being the only place where rows and cells are defined.

The rewriteable part of the article is this: create a helper class that subclasses NSObject and conforms to UITableViewDelegate and UITableViewDataSource. That class owns the item array and implements the table callbacks for any table that uses it.

class TableDataSource: NSObject, UITableViewDelegate, UITableViewDataSource {
    var items = [Item]()

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let item = items[indexPath.row]
        let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
        cell.textLabel?.text = item.title
        cell.detailTextLabel?.text = item.subtitle
        return cell
    }
}
Why It Helps The controller now wires tables together, while the helper owns row count and cell rendering.

The post also sketches three built-in cell configurations: title only, title plus subtitle, and subtitle plus image.

One useful part of the original tutorial is that it stays with system-generated UITableViewCell styles rather than introducing a custom nib or subclass too early.

// Title only
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = item.title

// Title + subtitle
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = item.title
cell.detailTextLabel?.text = item.subtitle

// Title + subtitle + image
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = item.title
cell.detailTextLabel?.text = item.subtitle
cell.imageView?.image = UIImage(systemName: "person.circle.fill")

This article sketches the idea informally in one example, but the full reusable implementation below settles on .subtitle, which is the practical choice when you want both a main label and a detail label.

The concrete demo uses one simple model and two separate data-source instances, one for cats and one for dogs.

The article's sample model is deliberately small:

struct Item {
    var title: String
    var subtitle: String
}

The controller then owns two table outlets and two independent data-source objects:

@IBOutlet weak var tableView1: UITableView!
var tableView1Data = TableDataSource()

@IBOutlet weak var tableView2: UITableView!
var tableView2Data = TableDataSource()

Finally, each helper gets a different dataset. This article uses sample animal names in Japanese; the important part is just that the arrays are different.

let cat1 = Item(title: "Nekonohi", subtitle: "Cat")
let cat2 = Item(title: "Mugi", subtitle: "Cat")
let cat3 = Item(title: "Leo", subtitle: "Cat")

let dog1 = Item(title: "Sora", subtitle: "Dog")
let dog2 = Item(title: "Maru", subtitle: "Dog")

tableView1Data.items = [cat1, cat2, cat3]
tableView2Data.items = [dog1, dog2]

Each table view gets its own delegate and data source object. That is the whole pattern.

After the objects and arrays exist, the controller only has to wire them up:

// tableView1
tableView1.delegate = tableView1Data
tableView1.dataSource = tableView1Data

// tableView2
tableView2.delegate = tableView2Data
tableView2.dataSource = tableView2Data

There is no special multi-table API here. The trick is simply that each table gets a separate object instance, so the two lists do not fight over the same backing data.

The demo screen ends up with two visibly separate lists on one screen, while the controller stays much smaller.

An iPhone screenshot showing two table views with separate sections of sample data
The same reusable table-data-source pattern drives both lists, but each one gets its own items array.

This pattern is really about separation of responsibility: the controller arranges the screen, and each helper object owns one table's behavior.

That is the useful takeaway from this article. Once tableDataSource is reusable, adding multiple UITableView instances to one UIViewController stops feeling like copy-paste work and starts looking like simple composition.