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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
iOS Storyboards: Segues and More
30 mins
- Getting Started
- Bringing the Players Screen to Life
- Storyboard, Please Meet My New Class
- Dynamic Cells
- Defining a New Cell Class
- Binding the Players
- More Players, Please
- Unwind Segues
- Creating the New Player
- Creating GamePicker View Controller
- Selecting the Game
- Passing Parameters With Segues
- Synchronizing Segues With Actions
- Saving the New Player
- First Responders
- Performance With Storyboards
- Storyboard References
- Where to Go From Here?
Selecting the Game
Tapping any of the games won’t do anything except give the cell a gray background. Definitely, that’s not the desired behavior. What it should do is:
- Select the new game.
- Return back to the form.
- Provide the form the selected game
You’ll do the second step first since its easier. Open PlayerDetailsViewController.swift and add the following code at the end of the file:
extension PlayerDetailsViewController {
@IBAction func unwindWithSelectedGame(segue: UIStoryboardSegue) {
}
}
This is a simple Unwind segue to return back to the Add Player Scene.
Return to Main.storyboard and Control-drag from the GameCell to the Exit icon above the scene. Select unwindWithSelectedGameWithSegue: under Selection Segue.
Build and run. Reach the games list and select any of the games.
The games list will automatically exit once you select any item, and will return to the previous scene. Wouldn’t it be cool if the list can show what has already been selected?
Passing Parameters With Segues
Chess is already set as the default game, so why is it not showing a checkmark beside it when the games list appears? To implement this, the Add Player Scene needs to pass the game name to Choose Game Scene whenever it opens. But the storyboard is already handling opening the scene itself, and there is no code for this at all. But there’s a hook.
When a segue is triggered, the view controller that triggered it will call the method prepare(for:sender:)
and will provide the segue that it’s handling. There are two important properties in the segue you’ll frequently use: identifier
and destination
. The identifier is just a string, just like the cell identifier you already used. It’s also empty by default. The destination is the view controller instance that the segue will go to.
Once you know which segue is happening from its identifier, you can cast the destination object to the view controller class you know. Let’s try that.
Open Main.storyboard and select the segue named Show segue to “Choose Game”. Then from the Attributes inspector change its Identifier to PickGame.
Then, in PlayerDetailsViewController.swift, add the following inside the class after viewDidLoad()
:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PickGame",
let gamePickerViewController = segue.destination as? GamePickerViewController {
gamePickerViewController.gamesDataSource.selectedGame = game
}
}
If the segue’s identifier
is PickGame, cast the destination view controller as GamePickerViewController
so you can access its properties. Set the current value of game
as the selected game.
Finally, in GamePickerViewController.swift, add the following in tableView(_:cellForRowAt:)
right before the return:
if indexPath.row == gamesDataSource.selectedGameIndex {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
Build and run. Open the games list screen and you’ll see that Chess is already marked.
To finalize the game selection, in GamePickerViewController.swift, add the following at the end of the file:
extension GamePickerViewController {
override func tableView(
_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath
) {
// 1
tableView.deselectRow(at: indexPath, animated: true)
// 2
if let index = gamesDataSource.selectedGameIndex {
let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0))
cell?.accessoryType = .none
}
// 3
gamesDataSource.selectGame(at: indexPath)
// 4
let cell = tableView.cellForRow(at: indexPath)
cell?.accessoryType = .checkmark
}
}
Whenever a cell in a table view is selected, tableView(_:didSelectRowAt:)
will execute the following steps:
- Remove the gray selection background that appears by default.
- Get the previously selected game and remove the checkmark from its cell.
- Select the new game in the data source.
- Mark the new cell with the checkmark.
Open PlayerDetailsViewController.swift and add the following within unwindWithSelectedGame(segue:)
:
if let gamePickerViewController = segue.source as? GamePickerViewController,
let selectedGame = gamePickerViewController.gamesDataSource.selectedGame {
game = selectedGame
}
If the source view controller of the segue is of type GamePickerViewController
and there is a valid selected game in the data source, then set the game to the new one.
Build and run, and choose a game other than chess. The value Chess is not updated! Why is that?
Synchronizing Segues With Actions
When you select a cell, the unwind segue is triggered first, and the PlayerDetailsViewController is reading the selected game from the data source before tableView(_:didSelectRowAt:)
in GamePickerViewController is executed. That’s the reason nothing has changed.
Automatically triggered segues are not meant to work hand-in-hand with actions when the order of execution is important. To fix this, you need to tell the view controller when to trigger the segue.
UIViewController
has performSegue(withIdentifier:sender:)
. Its job is to allow you to trigger a segue when you want it triggered.
To fix the issue, first remove the segue that the cell triggers on selection. Select the Exit icon in Choose Game Scene, and from the Connections inspector remove the action connected to the Selection outlet by clicking on the small x.
The segue needs to connect from the view controller itself so nothing else triggers it. To create a segue from the controller Control-drag from the View Controller icon to the Exit icon. Give this new segue the identifier unwind to reference it from the code.
Open GamePickerViewController.swift and, at the end of tableView(_:didSelectRowAt:)
, add the following:
performSegue(withIdentifier: "unwind", sender: cell)
Build and run and try to change the game. It works this time. :]
Time to resume the task of saving a new player.
Saving the New Player
The last step is to actually save the new Player object when the user taps Done. These are the actions needed to finish this step:
- Only when the segue connected to the Done button is triggered, construct a new Player object and store it in the controller.
- When the unwind segue of the Done button is performed, append that stored object to the data source.
Open Main.storyboard and, under Add Player Scene, select the unwind segue triggered by Done. Its name is Unwind segue to savePlayerDetail: in the Document Outline. Give it the identifier SavePlayerDetail.
Open PlayerDetailsViewController.swift. Add this property at the top of the class:
var player: Player?
Then, add the following at the beginning of prepare(for:sender:)
in the same file:
if segue.identifier == "SavePlayerDetail",
let playerName = nameTextField.text,
let gameName = detailLabel.text {
player = Player(name: playerName, game: gameName, rating: 1)
}
This creates a new Player
if the form is complete.
In PlayersViewController.swift, add the following as the implementation of savePlayerDetail(_:)
guard
let playerDetailsViewController = segue.source as? PlayerDetailsViewController,
let player = playerDetailsViewController.player
else {
return
}
playersDataSource.append(player: player, to: tableView)
Build and run, then add a new player in your app. It works like a charm! :]