Now that we have a Table View set up in Interface Builder, we need to tell it how to behave. The Table View has a custom Cell, but it doesn’t know how many cells to show or what content to put in those cells.

Enter UITableViewDelegate and UITableViewDataSource

UITableViewDelegate and UITableViewDataSource are two delegate protocols that let us tell the table view how to behave. Delegates allow an object to delegate responsibility and decision-making to other parts of your app. For example, the Table View doesn’t know how many rows it should have. It will ask “Hey, how many rows should I have?” and your app will say “You should have 3 rows.”

Conforming to UITableViewDelegate and UITableViewDataSource

We need to create a delegate that we can give to the Table View. It’s common practice to have the UIViewController double as your delegate, so go ahead and open your ViewController.swift

UITableViewDelegate and UITableViewDataSource are two protocols that we want our ViewController to implement. (If you’re more familiar with Java, think along the lines of implementing an Interface). Go ahead and add the protocols to the class declaration:

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // Xcode autogenerates some boilerplate code for you here,
    // but you can go ahead and delete it if you want.

}

You should start seeing some compile errors because ViewController does not conform to the protocol yet. We need to implement the protocol’s required methods.

How many rows do we want?

One of the protocol’s required methods is tableView(_, numberOfRowsInSection). Before we can tell the table views how many rows it should have, we need to decide that for ourselves.

It’s pretty typical to back a Table View by a data structure like an Array or a Dictionary. A Dictionary makes sense in this context, because we can use the person’s name and their current score as the key/value pair.

var scores = ["Cal" : 0, "Brian" : 0, "Komal" : 0]

Why var?

Most declarations in Swift are written with the let keyword. This means they’re immutable and can never change once initialized. This allows for some great compiler optimizations, but isn’t always what we want.

Later, we’ll be mutating the scores dictionary by incrementing and decrementing people’s score values. We want to use a var declaration so that the dictionary will be mutable, or capable of changing.

numberOfRowsInSection

Now we know that the table view should have the same number of rows as there are entries in the scores dictionary:

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

cellForRowAtIndexPath

The Table View knows how many rows it should have, but it still needs to know what those individual rows are. In our case, we want all of the rows to be the score cell that we made in Interface Builder, but you could set it up so every row was a different type of cell.

The tableView(_, cellForRowAt indexPath) method is supposed to return an instance of UITableViewCell. We want to create an instance of the cell we designed earlier, so we need to dequeue a reusable cell with the score identifier.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return tableView.dequeueReusableCell(withIdentifier: "score", for: indexPath)
}
Reusable cells

Imagine quickly scrolling through a table view that has 1000 rows. If the table view had to initialize a new UITableViewCell every time a new one came on screen, it would spend a lot of processing time on allocating and deallocating memory for these temporary objects. Extra processing time is the enemy of smooth performance, and those wasted CPU cycles would start causing noticeable stuttering.

Instead, UITableView keeps a queue of cells that have already been initialized. When you call dequeueReusableCell, the Table View checks its queue. If it has extra cells that have moved off-screen, it reuses those cells. It only initializes a new object if absolutely necessary.

Checking our progress

Your View Controller class should look something like this:

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    var scores = ["Cal" : 0, "Brian" : 0, "Komal" : 0]

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return tableView.dequeueReusableCell(withIdentifier: "score", for: indexPath)
    }
    
}

But if you run the app in the simulator, you’ll see nothing but an empty Table View:

Even though we’ve turned our ViewController into a UITableViewDelegate and UITableViewDataSource, we haven’t told the Table View to use that controller as its delegate.

Open up the Main.storyboard again, and select the Table View. Open the Connections Inspector, and you should see these outlets for dataSource and delegate:

We need to connect the View Controller to these outlets. You can do that by dragging from the circles on the right to the little yellow circle at the top of the canvas.

You should have both of them set to the View Controller:

If you run the app in the simulator again, you should actually see some content:

Why are they all the same?

The Table View is loading some score cells and displaying them, but we aren’t telling it how to render the cell. Right now it’s just displaying whatever we have in Interface Builder. We want to populate each row with a different person from the scores dictionary, but that’ll take a little more work.

Recap

We implemented our UITableViewDelegate / UITableViewDataSource. We covered UITableView’s optimization strategy and got our custom Cell to start appearing on screen.

Next Time

We’ll implement a custom class that subclasses UITableViewCell, allowing us to customize the content in each individual row.