Managing Multiple UITableViews

I just finished an overview on setting up multiple UITableViews on the same viewController and how to implement a draggable card view that can present dynamic data, but I want to break down these concepts so I can spend the time to explain them instead of just giving a high level overview.

There are a lot of times where using multiple sections on a UITableView is the right call to make; however, if you need to have 2 or more UITableViews on the same viewController for any reason, it turns out it’s pretty simple.

Step 1: Declare Your UITableViews And Address Datasources.

    var mainTableView       : UITableView! // main table view to display all characters
    var cardTableView       : UITableView! // table for specific character data in card view
    var data                = [Character]() // data for your main table
    var character           : Character? // the individual character you select to show
    var selectionOfData     = ["Gender: ", "House: ", "Actor: ", "Patronus: "] // the fields to show on your card view's table

In this particular scenario, I’m using an array of Character objects I’m getting from a Harry Potter API.

I’m using selectionOfData to supply my second UITableView, the one in the draggable card view, and I’ve set placeholders here until they’re replaced in the main table’s didSelectRowAt method.

Step 2: Set Up Your UITableViews.

    var rowHeight : CGFloat = 50     // use this to keep keep card limited in height based on cardRowHeight

I’m using a variable here because I have some other variables that are dependent on this value, so if someone needs to change the cell height all they have to do is change this one variable and all of the subsequent calculations will still work. (This will make more sense when you see the finished product, as it relates to the position of the sliding card view).

    private func tableSetUp() {
        mainTableView = UITableView()
        mainTableView.backgroundColor = .clear
        mainTableView.tableFooterView = UIView()
        view.addSubview(mainTableView)
        view.sendSubviewToBack(mainTableView)
        mainTableView.translatesAutoresizingMaskIntoConstraints = false
        mainTableView.register(CharacterCell.self, forCellReuseIdentifier: CharacterCell.reuseID)
        mainTableView.rowHeight = rowHeight
        NSLayoutConstraint.activate([
            mainTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            mainTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            mainTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50),
            mainTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
        ])
    }
    
    private func cardTableViewSetUp() {
        cardTableView = UITableView()
        cardTableView.backgroundColor = .systemTeal
        cardTableView.tableFooterView = UIView()
        cardTableView.rowHeight = rowHeight
        cardView.addSubview(cardTableView)
        cardTableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            cardTableView.leadingAnchor.constraint(equalTo: cardView.bodyView.leadingAnchor),
            cardTableView.trailingAnchor.constraint(equalTo: cardView.bodyView.trailingAnchor),
            cardTableView.topAnchor.constraint(equalTo: cardView.bodyView.topAnchor),
            cardTableView.bottomAnchor.constraint(equalTo: cardView.bodyView.bottomAnchor)
        ])
        cardTableView.register(CharacterCell.self, forCellReuseIdentifier: CharacterCell.reuseID)
    }

I’m specifically setting this second table on the card view that I’ve already created, but if you haven’t seen the full project yet that’s fine. You can put these two tables anywhere you’d like; as long as they’re both managed by the same viewController, this tutorial will be applicable.

Step 3: Create An Extension To Subclass UITableViewDatasource And UITableViewDelegate

extension TabOneVC : UITableViewDelegate, UITableViewDataSource {
}

Create your extension to manage your tables’ delegate and dataSource methods.

Step 4: The Magic

Step 4.1: Set both tables’ delegates and datasources to the viewController

    private func tableSetUp() {
...
        mainTableView.delegate = self
        mainTableView.dataSource = self
...
    }
    
    private func cardTableViewSetUp() {
...
        cardTableView.delegate = self
        cardTableView.dataSource = self
...
    }

Add the lines above to your two tables. You’re telling both tables to look for their delegates and datasources here, on this single viewController.

Step 4.2: Conditional Statements To The Rescue!

In this example, I’m only using one UITableViewCell because for the basic need here I can get away with it and why duplicate if you don’t need to?

However, when it comes to populating the data for that cell, it’s completely different depending on the table, and so I run a conditional statement like so:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: CharacterCell.reuseID) as! CharacterCell
        if tableView == self.mainTableView {
            cell.set(title: data[indexPath.row].name, alignment: .center)
            if let character = character, character.name == data[indexPath.row].name {
                cell.label.textColor = .white
            } else {
                cell.label.textColor = .black
            }
        } else {
            cell.set(title: selectionOfData[indexPath.row], alignment: .left)
        }
        return cell
    }

With the conditional statement comparing the local tableView to the viewController’s mainTableView, it will allow you to set your cells appropriately for both UITableViews.

I also added in the functionality to change a cell’s label text to white if that cell is the currently selected cell, but that’s not important here.

And then I add the following code within this method as well:

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if tableView == self.mainTableView {
            return data.count
        } else {
            return selectionOfData.count
        }
    }

With these, I’m easily able to pull from the correct datasource and build the correct cells.

I added the didSelectRowAt method as well so that whenever the main table’s cells are selected they update the secondary table accordingly.

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if tableView == self.mainTableView {
            character = data[indexPath.row]
            let cell = tableView.cellForRow(at: indexPath) as! CharacterCell
            cell.label.textColor = .white
            selectionOfData = ["Gender: " + character!.gender.rawValue, "House: " + character!.house, "Actor: " + character!.actor, "Patronus: " + character!.patronus]
            cardTableView.reloadData()
            UIView.animate(withDuration: 0.1, animations: {
                self.cardView.frame = self.cardView.frame.offsetBy(dx: 0, dy: -10)
            }) { (true) in
                self.cardView.frame = self.cardView.frame.offsetBy(dx: 0, dy: 10)
            }
        }
    }

In this, I am:

  1. checking to make sure I’m only working with the main UITableView
  2. setting the character to the character associated with the cell I’ve clicked
  3. setting the text color to white
  4. setting the selectionOfData array to reflect the cell’s Character’s information.
  5. reloading the secondary table’s data
  6. I also add a quick little animation so that the table will be noticed if you click on a cell and the card view is closed.

And what we have here, when coupled with the sliding card view that I’ll address in the next tutorial, is a handy way to get information about the characters from Harry Potter!

Full project here: https://github.com/BodhiMoosa/DisplayCardExample