iOS Storyboards: Segues and More

In this tutorial, you’ll learn how to connect view controllers to a storyboard, programmatically trigger segues, control visual components and respond to the user interactions. By Ehab Amer.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Binding the Players

The first option is to directly set the values on the cell inside tableView(_:cellForRowAt:). The second is to pass a player instance to the cell and let the cell extract values from it.

Sometimes, the first option is more convenient and easier, but frequently you’ll end up having a lot of code in tableView(_:cellForRowAt:). For the players, apply the second option.

In PlayerCell.swift, add the following right after the outlet declarations:

// 1:
var player: Player? {
  didSet {
    guard let player = player else { return }

    gameLabel.text = player.game
    nameLabel.text = player.name
    ratingImageView.image = image(forRating: player.rating)
  }
}

// 2:
private func image(forRating rating: Int) -> UIImage? {
  let imageName = "\(rating)Stars"
  return UIImage(named: imageName)
}

The first is a property of type Player, with a block that gets executed when its value is set. Such a block will set the values on the three components connected to the cell. The second is a method to fetch the appropriate image based on the rating of the player.

Now, go to PlayersViewController.swift, and right before the return of the cell in tableView(_:cellForRowAt:) add this:

cell.player = playersDataSource.player(at: indexPath)

Wait… What is this error!?

Error Message

You added the player to the cell class, so why is Xcode complaining about it?

Right now, the return type of cell is still a UITableViewCell.

Cell class

dequeueReusableCell(withIdentifier:for:) returns an instance of a cell with the identifier you provide as a type of UITableViewCell. But since you specified the class of that cell is PlayerCell, it’s safe to cast it accordingly.

All you need to do is change the line that creates the cell to the following:

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

Now, Xcode is happy. Build and run and celebrate your first working screen! :]

Simulator Screenshot

Your app is now listing actual players. The next step is to add more players.

More Players, Please

Create a new Swift File named PlayerDetailsViewController.swift in the View Controllers folder and replace the import line with the following:

import UIKit

class PlayerDetailsViewController: UITableViewController {
  @IBOutlet weak var nameTextField: UITextField!
  @IBOutlet weak var detailLabel: UILabel!
}

Then, from Main.storyboard, set the class of the Add Player Scene to PlayerDetailsViewController.

Before attaching the outlets of the new view controller, there is something you should be aware of. You can’t attach outlets in a view controller to views in dynamic cells. Those cells won’t be displayed when the view controller is initialized. But for static cells, everything is set in the storyboard, so you can safely attach views inside static cells to outlets in the view controller.

Connect detailLabel to the label in the second cell titled Detail, and nameTextField to the text field in the first cell.

Connectiong Outlets

Now, in PlayerDetailsViewController.swift add this property before the declaration of the outlets.

var game = "" {
  didSet {
    detailLabel.text = game
  }
}

This will save the name of the game. Any time this property value is updated, the label will also be updated.

Finally, add this at the end of the class:

override func viewDidLoad() {
  game = "Chess"
}

The view controller will call viewDidLoad() once when it loads all its views in memory. Since updating the game value will update the view, it makes sense to do this when the views finished loading.

Note: Outlets will have values after the view controller loads the views and executes viewDidLoad(), not with the initialization.

Build and run, tap the + button in the upper right corner to open the add player screen.

Simulator Add Player Screen

It doesn’t feel that much has changed except for the the game is now Chess. Before you add the functionality to add players, fix it so the Cancel and Done buttons do something.

Unwind Segues

Both Cancel and Done have a common behavior of returning back to the listing screen. Done will do more than just that, but start with that common action.

In PlayersViewController.swift, add the following extension:

extension PlayersViewController {
  @IBAction func cancelToPlayersViewController(_ segue: UIStoryboardSegue) {
  }

  @IBAction func savePlayerDetail(_ segue: UIStoryboardSegue) {
  }
}

Return to Main.storyboard. Control-drag from the Cancel button to the third icon in the bar above the scene in the storyboard. Select cancelToPlayersViewController: from the pop-up. Do the same for the Done button, but select savePlayerDetail:.

Adding Unwind Segues

Build and run. Open the add player screen and try the two buttons.

Segues in Action

It worked, but how?

The two methods you added are Unwind Segues. They are basically exit segues to the view controller they are implemented on. Each button is using its own segue to return to the PlayersViewController.

Xcode identifies Unwind segues from their unique signature: A method that takes one parameter of type UIStoryboardSegue.

When you tap the + button from the listing screen, it triggers a segue to present a navigation controller, which in turn triggers a segue to show the add player scene. Unwind segues will automatically reverse all the segues from the scene you are exiting back to the scene you’re returning to.

You could have added only one method instead of two, but you still need to add the code for saving the new player with the Done action. For now, you finished the Cancel action. Now, it’s time to actually save newly created players.

Creating the New Player

There are two steps to save new players:

  1. Capture the player information (name, game and rating) inside the form, and create a full player instance with it.
  2. Pass the instance to the listing screen to save and display.

Sounds simple, right? The add player scene already has a text field to write the player’s name, and it’s already connected to an outlet, so that’s ready. You can use any rating value. For now, use one star since it’s a new player. The only missing item is the game. It’s set as Chess currently, but it can’t stay like that permanently.

Creating GamePicker View Controller

When you tap on the second cell in the add player scene, it opens a list of games. But, when you select one, nothing happens. Neither is chess selected by default.

To be able to do anything, you need to have a class for this view controller. Create a new Swift File named GamePickerViewController.swift. Replace the import statement with the following:

import UIKit

class GamePickerViewController: UITableViewController {
  let gamesDataSource = GamesDataSource()
}

Nothing special here, almost identical to the players list. A new type that subclasses UITableViewController, and contains a property that acts as the data source for games.

Open Main.storyboard, select the Choose Game Scene and change its class to GamePickerViewController.

Then select its Table View and change its Content value to Dynamic Prototypes, and the number of Prototype Cells to 1.

Attributes Inspector

Set the Identifier of the only cell left to GameCell.

Add the following at the end of GamePickerViewController.swift:

extension GamePickerViewController {
  override func tableView(
    _ tableView: UITableView,
    numberOfRowsInSection section: Int
  ) -> Int {
    gamesDataSource.numberOfGames()
  }

  override func tableView(
    _ tableView: UITableView,
    cellForRowAt indexPath: IndexPath
  ) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "GameCell", for: indexPath)
    cell.textLabel?.text = gamesDataSource.gameName(at: indexPath)

    return cell
  }
}

The first method returns the number of games the data source has. And the second creates a cell using the identifier you have set in the storyboard, then sets the text of textLabel to the game’s name.

Build and run, reach the games list, and it should look the same as before you applied any changes.

Simulator: GamePickerViewController

The Games list now shows the same data, but instead of being set on the storyboard, they are now loaded programmatically from gamesDataSource. Notice that you didn’t subclass the cell this time.

The cells in this scene have their Style set to Basic. That style gives the cell a default text label accessible through the property textLabel.

Cell Style Selection