Now that we have a reference to the cell’s label, we can customize it on-demand. We want to add a new method to the ScoreCell class that takes a person’s name and can update the cell appropriately. Here’s what we want:

func decorate(for name: String, in controller: ViewController) {
    let score = controller.scores[name] ?? 0
    self.label.text = "\(name): \(score)"
}

The function takes a name and the ViewController object in order to decorate the cell as desired. We could have passed in the score explicitly instead of using the ViewController as a middle-man, but this choice will come in handy later.

To take the function line-by-line:

let score = controller.scores[name] ?? 0

This line uses the controller argument to access the View Controller’s scores dictionary. scores[name] returns the score associated with the given name. Accessing a value from a dictionary returns an Optional, though, because it’s possible that the name doesn’t exist in the dictionary. In that case, scores[name] would return nil. Adding ?? 0, the nil-coalescing operator, applies a default value whenever the value to the left is nil.

To sum it up in one sentence, the line either retrieves the score associated with the name or uses 0 if that name doesn’t exist in the scores dictionary.

self.label.text = "\(name): \(score)"

This line builds the string we want to display on the label. The \( ) is Swift’s special String Interpolation syntax, which allows you to insert variables into strings. This could have been done with name + ": " + String(score), but string interpolation is more ~swifty~.

Using the decorate method

Now that the ScoreCell is customizable, we can revisit the cellForRowAtIndexPath method. We’ll rewrite it to look like this:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "score", for: indexPath) as! ScoreCell
    
    let row = indexPath.row
    let name = Array(scores.keys)[row]
    cell.decorate(for: name, in: self)
    
    return cell
}

Again, we’ll take a line-by-line look:

let cell = tableView.dequeueReusableCell(withIdentifier: "score", for: indexPath) as! ScoreCell

This line is mostly the same as what we had before, but there’s two differences. First off, we save a reference to the cell so that we can do cell.decorate later. The as! ScoreCell is crucial, though. The dequeueReusableCell function returns a UITableViewCell object, but we can only access the decorate method on a ScoreCell object.

We have to up-cast the cell to a ScoreCell. We know that our cell with the score identifier will always be a ScoreCell, but that’s not enough for the compiler. Marking the cast with an as! tells the compiler to ignore the possibility that the cell might not be a ScoreCell. This lets us not have to worry about unwrapping an optional. as! is an unsafe operation and will crash your app if it fails, but it’s safe to use in this context because we know it should always succeed.

let row = indexPath.row

An NSIndexPath is a small object that contains a section and a row. Table Views can have multiple sections, but our table view only has one. We get the row from the indexPath and save it for the next line.

let name = Array(scores.keys)[row]

On this line, we grab a name out of the scores dictionary. The problem with dictionaries, though, is that they’re inherently unordered. Arrays(scores.keys) converts our unordered dictionary ["Cal" : 0, "Brian" : 0, "Komal" : 0] into an explicitly ordered array, ["Cal", "Brian", "Komal"]. The order of the array’s elements will be arbitrary, but it’s good enough for this app. Once we have an array, we can access the n-th item through standard array-accessor syntax.

cell.decorate(for: name, in: self)

This line is where we pull it all together. We actually call the decorate method on our Score Cell, passing the name and the ViewController as expected.

return cell

After we’ve populated the cell with content, we return it so the Table View can start using it.

Checking our progress

If you run the app in the simulator, the rows should actually match the dictionary now:

And if you update the dictionary to something like var scores = ["Cal" : 1, "Brian" : 5, "Komal" : 2, "Kevin" : 3, "Luke" : 0, "Tim" : 4, "Steve" : 6, "Bill" : 0], you should see the changes reflected when you re-run the app:

Recap

You wrote a decorate function that lets the ViewController populate individual cells with content.

Next Time

We’ll wrap up by implementing the add and subtract buttons.