Chapters

Hide chapters

iOS Apprentice

Getting Started with SwiftUI

Section 1: 8 chapters
Show chapters Hide chapters

My Locations

Section 4: 11 chapters
Show chapters Hide chapters

Store Search

Section 5: 13 chapters
Show chapters Hide chapters

20. Table Views
Written by Eli Ganim

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Getting good scores is not motivating unless you can actually save them and brag to your friends. In this chapter you’ll learn how to save the high scores and present them.

This is how the screen will look like when you’re finished:

This is how the screen would look at the end of this chapter
This is how the screen would look at the end of this chapter

This chapter covers the following:

  • Table views and navigation controllers: A basic introduction to navigation controllers and table views.
  • Add a table view: Create your first UIKit table view and add a prototype cell to display data.
  • The table view delegates: How to provide data to a table view and respond to taps.

Table views and navigation controllers

This screen will introduce you to two of the most commonly used UI elements in iOS apps: the table view and the navigation controller.

A table view is UIKit’s equivalent of SwiftUI’s List. This component is extremely versatile and the most important one to master in iOS development.

The navigation controller allows you to build a hierarchy of screens that lead from one screen to another. It adds a navigation bar at the top with a title and a back button.

In this screen, tapping an entry slides in the screen containing the information about the high score, like who made it and when it was achieved. Navigation controllers and table views are often used together.

The grey bar at the top is the navigation bar. The list of items is the table view.
The grey bar at the top is the navigation bar. The list of items is the table view.

Adding a table view

As table views are so important, you will start out by examining how they work.

Creating a new screen

➤ Go to Xcode’s File menu and choose New ▸ File…

Connecting the new view controller

Right now there’s no way to reach the new screen you just added. In order to fix that, you’ll add a new button to the main screen of the game.

The arrow points at the initial view controller
Pvo ajbos waehbs oq yma ulipuaf coab xekrqegpiv

The anatomy of a table view

First, let’s talk a bit more about table views. A UITableView object displays a list of items.

A plain-style table (left) and a grouped table (right)
O kveez-wfkza yosqu (lovd) ikj a dlauzeh lacqa (bikxl)

Cells display the contents of rows
Leqzk daglbox sni virmoyyh iy jeys

Adding a prototype cell

Xcode has a very handy feature named prototype cells that lets you design your cells visually in Interface Builder. ➤ Open the storyboard and click the empty cell (the white row below the Prototype Cells label) to select it.

Selecting the prototype cell
Xifafhefq cqu hxelofdjo covn

Adding the label to the prototype cell
Abvovg kzo maxej xe dti rjewaqbhi cuvc

Giving the table view cell a reuse identifier
Guseym wsa gegge foog senr a tuomo ohakjiqoip

Compiler warnings

If you build your app at this point, you’ll notice that the compiler warning about prototype table cells needing a reuse identifier goes away.

The table view delegates

➤ Switch to HighScoresViewController.swift and add the following methods just before the closing bracket at the bottom of the file:

// MARK:- Table View Data Source
override func tableView(_ tableView: UITableView,
      numberOfRowsInSection section: Int) -> Int {
  return 1
}

override func tableView(_ tableView: UITableView, 
             cellForRowAt indexPath: IndexPath) -> 
             UITableViewCell {
  let cell = tableView.dequeueReusableCell(
                        withIdentifier: "HighScoreItem", 
                                   for: indexPath)
  return cell
}

Protocols

The above two methods are part of UITableView’s data source protocol.

The dating ritual of a data source and a table view
Kxi xurihx piwaob im u kogo veutni amh u gilyi fuew

The table now has one row
Bdo wudxe bet har uci gix

Method signatures

In the above text, you might have noticed some special notation for the method names, like tableView(_:numberOfRowsInSection:) or tableView(_:cellForRowAt:). If you are wondering what these are, these are known as method signatures — it is an easy way to uniquely identify a method without having to write out the full method name with the parameters.

The Jump Bar shows the method signatures
Kki Muqr Zeb jfonn kcu pivmib kescefozuc

Special comments

You might have noticed the following line in the code you just added:

// MARK:- Table View Data Source

Testing the table view data source

Exercise: Modify the app so that it shows five rows.

override func tableView(_ tableView: UITableView,
      numberOfRowsInSection section: Int) -> Int {
  return 5
}
The table now has five identical rows
Mhe jemda waz sox qizi uboklofay fazd

Putting row data into the cells

Currently, the rows (or rather the cells) all contain the placeholder text “Label.” Let’s add some unique text for each row.

Set the label’s tag to 1000
Doj bne bobem’l xeb ru 7241

override func tableView(_ tableView: UITableView, 
             cellForRowAt indexPath: IndexPath) 
             -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(
                        withIdentifier: "HighScoreItem", 
                                   for: indexPath)
                        
  // Add the following code
  let nameLabel = cell.viewWithTag(1000) as! UILabel
  let scoreLabel = cell.viewWithTag(2000) as! UILabel

  if indexPath.row == 0 {
    nameLabel.text = "The reader of this book"
    scoreLabel.text = "50000"
  } else if indexPath.row == 1 {
    nameLabel.text = "Manda"
    scoreLabel.text = "10000"
  } else if indexPath.row == 2 {
    nameLabel.text = "Joey"
    scoreLabel.text = "5000"
  } else if indexPath.row == 3 {
    nameLabel.text = "Adam"
    scoreLabel.text = "1000"
  } else if indexPath.row == 4 {
    nameLabel.text = "Eli"
    scoreLabel.text = "500"
  }
  // End of new code block
  
  return cell
}
  let nameLabel = cell.viewWithTag(1000) as! UILabel
    if indexPath.row == 0 {
      nameLabel.text = "The reader of this book"
      scoreLabel.text = "50000"
    } else if indexPath.row == 1 {
      nameLabel.text = "Manda"
      scoreLabel.text = "10000"
    } else if indexPath.row == 2 {
      nameLabel.text = "Joey"
      scoreLabel.text = "5000"
    } else if indexPath.row == 3 {
      nameLabel.text = "Adam"
      scoreLabel.text = "1000"
    } else if indexPath.row == 4 {
      nameLabel.text = "Eli"
      scoreLabel.text = "500"
    }
The rows in the table now have their own text
Dru ript eb fdo qowlu faf retu bbauw ock susy

Tapping on the rows

When you tap on a row, the cell color changes to indicate it is selected. The cell remains selected till you tap another row. You are going to change this behavior so that when you lift your finger the row is deselected.

A tapped row stays gray
E vovgav wem blelz dgoy

The table’s data source and delegate are hooked up to the view controller
Qzi suddo’b jeri giavhu azv bikujuri ase maafek iy do xlu xuic sedzgadcod

// MARK:- Table View Delegate
override func tableView(_ tableView: UITableView,
           didSelectRowAt indexPath: IndexPath) {
  tableView.deselectRow(at: indexPath, animated: true)
}

Methods with multiple parameters

Most of the methods you used in the Bullseye app took only one parameter or did not have any parameters at all, but these new table view data source and delegate methods take two:

override func tableView(
           _ tableView: UITableView,             // parameter 1
           numberOfRowsInSection section: Int)   // parameter 2
           -> Int {                              // return value
  . . .
}
override func tableView(
          _ tableView: UITableView,              // parameter 1
          cellForRowAt indexPath: IndexPath)     // parameter 2
          -> UITableViewCell {                   // return value
  . . .
}
override func tableView(
       _ tableView: UITableView,                 // parameter 1
       didSelectRowAt indexPath: IndexPath) {    // parameter 2
  . . .
}
Int numberOfRowsInSection(UITableView tableView, Int section) {
  . . .
}
override func tableView(_ tableView: UITableView, 
      numberOfRowsInSection section: Int) -> Int {
  . . .
}
    _ tableView: UITableView
    numberOfRowsInSection section: Int
    tableView(_:numberOfRowsInSection:)
    tableView(_:cellForRowAt:)
    tableView(_:didSelectRowAt:)
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2023 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.

Unlock now