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?
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!?
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
.
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! :]
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.
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.
viewDidLoad()
, not with the initialization.Build and run, tap the + button in the upper right corner to open the 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:.
Build and run. Open the add player screen and try the two buttons.
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:
- Capture the player information (name, game and rating) inside the form, and create a full player instance with it.
- 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.
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.
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
.