Storyboards Tutorial in iOS 7: Part 2
Storyboards tutorial to get you started with designing user interfaces for your iPhone apps on iOS 7. By Matthijs Hollemans.
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
Storyboards Tutorial in iOS 7: Part 2
40 mins
Note from Ray: Tutorial Team member Matthijs Hollemans (the iOS Apprentice Series author) has ported this popular tutorial from iOS 5 by Tutorials to iOS 7. This is a sneak peek of the third edition of the book, which will be updated to iOS 7. We hope you enjoy!
If you want to learn about storyboarding, you’ve come to the right place!
In the first part of the storyboards tutorial series, you covered the basics of using Interface Builder to create and connect various view controllers, along with how to make custom table view cells directly from the storyboard editor.
In this second and final part of the tutorial series, we’ll cover segues, static table view cells, the Add Player screen, and a game picker screen!
We’ll start where we left off last tutorial, so open your project from last time, or go through the previous tutorial first.
OK, let’s dive into some of the other cool features in Storyboarding!
Introducing Segues
It’s time to add more view controllers to the storyboard. You’re going to create a screen that allows users to add new players to the app.
Open up Main.storyboard and drag a Bar Button Item into the right slot of the navigation bar on the Players screen. In the Attributes inspector change its Identifier to Add to make it a standard + button.
When the user taps this button the app pops up a new modal screen for entering the details of a new player.
Drag a new Navigation Controller into the canvas to the right of the Players screen. Remember that you can double-click the canvas to zoom out so you have more room to work. This new Navigation Controller comes with a Table View Controller attached, so that’s handy.
Here’s the trick: Select the + button that you just added on the Players screen and ctrl-drag to the new Navigation Controller:
Release the mouse button and a small popup menu shows up:
Choose modal. This places a new arrow between the Players screen and the Navigation Controller:
This type of connection is known as a segue (pronounce: seg-way) and represents a transition from one screen to another. The The storyboard connections you’ve seen so far were relationships and they described one view controller containing another. A segue, on the other hand, changes what is on the screen. Segues are triggered by taps on buttons, table view cells, gestures, and so on.
The cool thing about using segues is that you no longer have to write any code to present the new screen, nor do you have to hook up your buttons to IBActions. What you just did, dragging from the Bar Button Item to the next screen, is enough to create the transition. (Note: If your control already had an IBAction connection, then the segue overrides that.)
Run the app and press the + button. A new table view will slide up the screen.
This is a so-called “modal” segue. The new screen completely obscures the previous one. The user cannot interact with the underlying screen until they close the modal screen first. Later on you’ll also see “push” segues that push new screens on the navigation stack of a Navigation Controller.
The new screen isn’t very useful yet — you can’t even close it to go back to the main screen. That’s because segues only go one way, from the Players screen to this new one. To go back, you have to use the delegate pattern. For that, you first have to give this new scene its own class.
Add a new file to the project and name it PlayerDetailsViewController, subclass of UITableViewController
. To hook this new class up to the storyboard, switch back to Main.storyboard and select the new Table View Controller scene (the one that says “Root View Controller”). In the Identity inspector set its Class to PlayerDetailsViewController. I always forget this very important step, so to make sure you don’t, I’ll keep pointing it out.
While you’re there, change the title of the screen to Add Player (by double-clicking in the navigation bar). Also add two Bar Button Items to the navigation bar. In the Attributes inspector, set the Identifier of the button to the left to Cancel, and the one on the right to Done (also change this one’s Style from Bordered to Done).
Then change PlayerDetailsViewController.h to the following:
@class PlayerDetailsViewController;
@protocol PlayerDetailsViewControllerDelegate <NSObject>
- (void)playerDetailsViewControllerDidCancel:(PlayerDetailsViewController *)controller;
- (void)playerDetailsViewControllerDidSave:(PlayerDetailsViewController *)controller;
@end
@interface PlayerDetailsViewController : UITableViewController
@property (nonatomic, weak) id <PlayerDetailsViewControllerDelegate> delegate;
- (IBAction)cancel:(id)sender;
- (IBAction)done:(id)sender;
@end
This defines a new delegate protocol that you’ll use to communicate back from the Add Player screen to the main Players screen when the user taps Cancel or Done.
Switch back to Interface Builder and hook up the Cancel and Done buttons to their respective action methods. One way to do this is to ctrl-drag from the bar button to the view controller and then picking the correct action name from the popup menu:
Make sure you pick the cancel: and done: actions from the Sent Actions section of the popup menu – don’t create a new segue!
In PlayerDetailsViewController.m, add the following two methods at the bottom of the file:
- (IBAction)cancel:(id)sender
{
[self.delegate playerDetailsViewControllerDidCancel:self];
}
- (IBAction)done:(id)sender
{
[self.delegate playerDetailsViewControllerDidSave:self];
}
These are the action methods for the two bar buttons. For now, they simply let the delegate know what just happened. It’s up to the delegate to close the screen. (That is not a requirement, but that’s how I like to do it. Alternatively, you could make the Add Player screen close itself before or after it has notified the delegate.)
Note: It is customary for delegate methods to include a reference to the object in question as their first (or only) parameter, in this case the PlayerDetailsViewController
. That way the delegate always knows which object sent the message.
Note: It is customary for delegate methods to include a reference to the object in question as their first (or only) parameter, in this case the PlayerDetailsViewController
. That way the delegate always knows which object sent the message.
Now that you’ve given the PlayerDetailsViewController
a delegate protocol, you still need to implement that protocol somewhere. Obviously, that will be in PlayersViewController
since that is the view controller that presents the Add Player screen. Add the following to PlayersViewController.h:
#import "PlayerDetailsViewController.h"
@interface PlayersViewController : UITableViewController <PlayerDetailsViewControllerDelegate>
Implement the delegate methods in PlayersViewController.m:
#pragma mark - PlayerDetailsViewControllerDelegate
- (void)playerDetailsViewControllerDidCancel:(PlayerDetailsViewController *)controller
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)playerDetailsViewControllerDidSave:(PlayerDetailsViewController *)controller
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Currently these delegate methods simply close the screen. Later you’ll make them do more interesting things.
There is only one thing left to do to make all of this work: the Players screen has to tell the PlayerDetailsViewController
that it is now its delegate. That seems like something you could set up in Interface Builder just by dragging a line between the two. Unfortunately, that is not possible. To pass data to the new view controller during a segue, you still need to write some code.
Add the following method to PlayersViewController.m (it doesn’t really matter where):
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@"AddPlayer"]) {
UINavigationController *navigationController = segue.destinationViewController;
PlayerDetailsViewController *playerDetailsViewController = [navigationController viewControllers][0];
playerDetailsViewController.delegate = self;
}
}
The prepareForSegue:
method is invoked whenever a segue is about to take place. The new view controller has been loaded from the storyboard at this point but it’s not visible yet, and you can use this opportunity to send data to it.
Note: You never call prepareForSegue:
yourself. It’s a message from UIKit to let you know that a segue has just been triggered.
Note: You never call prepareForSegue:
yourself. It’s a message from UIKit to let you know that a segue has just been triggered.
The destination of this particular segue is the Navigation Controller, because that is what you connected to the Bar Button Item. To get the PlayerDetailsViewController
instance, you have to dig through the Navigation Controller’s array of viewControllers
to find it.
Run the app, press the + button, and try to close the Add Player screen. It still doesn’t work!
That’s because you never gave the segue an identifier. The code from prepareForSegue checks for that identifier (“AddPlayer”). It is recommended to always do such a check because you may have multiple outgoing segues from one view controller and you’ll need to be able to distinguish between them (something that you’ll do later in this tutorial).
To fix this issue, open Main.storyboard and click on the segue between the Players screen and the Navigation Controller. Notice that the Bar Button Item now lights up, so you can easily see which control triggers this segue.
In the Attributes inspector, set Identifier to AddPlayer:
Run the app again; tapping Cancel or Done will now properly close the screen and return you to the list of players.
Note: It is perfectly possible to call dismissViewControllerAnimated:completion: from the modal screen. There is no requirement that says the delegate must do this. I personally prefer to let the delegate handle this but if you want the modal screen to close itself, then go right ahead.
Note: It is perfectly possible to call dismissViewControllerAnimated:completion: from the modal screen. There is no requirement that says the delegate must do this. I personally prefer to let the delegate handle this but if you want the modal screen to close itself, then go right ahead.
By the way, the Attributes inspector for the segue also has a Transition field. You can choose different animations:
Play with them to see which one you like best. Don’t change the Style setting, though. For this screen it should be Modal — any other option will crash the app!
You’ll be using the delegate pattern a few more times in this tutorial. Here’s a handy checklist for setting up the connections between two scenes:
- Create a segue from a button or other control on the source scene to the destination scene. (If you’re presenting the new screen modally, then often the destination will be a Navigation Controller.)
- Give the segue a unique Identifier. (It only has to be unique in the source scene; different scenes can use the same identifier.)
- Create a delegate protocol for the destination scene.
- Call the delegate methods from the Cancel and Done buttons, and at any other point your destination scene needs to communicate with the source scene.
- Make the source scene implement the delegate protocol. It should dismiss the destination view controller when Cancel or Done is pressed.
- Implement
prepareForSegue
: in the source view controller and dodestination.delegate = self;
.
Delegates are necessary because — at least on iOS 5 — there is no such thing as a “reverse segue”. When a segue is triggered it always creates a new instance of the destination view controller. You can certainly make a segue back from the destination to the source, but that may not do what you expect.
If you were to make a segue back from the Cancel button to the Players screen, for example, then that wouldn’t close the Add Player screen and return to Players, but it creates a new instance of the Players screen. You’ve started an infinite cycle of creating new view controllers over and over that only ends when the app runs out of memory.
Remember: Segues only go one way; they are only used to open a new screen. To go back you dismiss the screen (or pop it from the navigation stack), usually from a delegate. The segue is employed by the source controller only. The destination view controller doesn’t even know that it was invoked by a segue.
Note: Does creating a delegate protocol for each screen that you want to reach through a segue sound like a lot of work? That’s what the creators of UIKit thought too, so in iOS 6 they introduced a new concept: the unwind segue. With this new feature you can create segues that go back to the previous screen. That is what the green Exit icon is for in the storyboard. If you want to learn more about unwind segues, we’ve dedicated a chapter to it in iOS 6 by Tutorials.
Note: Does creating a delegate protocol for each screen that you want to reach through a segue sound like a lot of work? That’s what the creators of UIKit thought too, so in iOS 6 they introduced a new concept: the unwind segue. With this new feature you can create segues that go back to the previous screen. That is what the green Exit icon is for in the storyboard. If you want to learn more about unwind segues, we’ve dedicated a chapter to it in iOS 6 by Tutorials.