Beginning Storyboards in iOS 5 Part 2

Note from Ray: This is the third iOS 5 tutorial in the iOS 5 Feast! This tutorial is a free preview chapter from our new book iOS 5 By Tutorials. Matthijs Hollemans wrote this chapter – the same guy who wrote the iOS Apprentice Series. Enjoy! This is a post by iOS Tutorial Team member […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share

Serve yourself with some Storyboards!

Note from Ray: This is the third iOS 5 tutorial in the iOS 5 Feast! This tutorial is a free preview chapter from our new book iOS 5 By Tutorials. Matthijs Hollemans wrote this chapter – the same guy who wrote the iOS Apprentice Series. Enjoy!

This is a post by iOS Tutorial Team member Matthijs Hollemans, an experienced iOS developer and designer.

If you want to learn more about the new storyboarding feature in iOS 5, you’ve come to the right place!

In the first part of the tutorial series, we covered the basics of using the new storyboard editor 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 new features in Storyboarding!

Introducing Segues

It’s time to add more view controllers to our storyboard. We’re going to create a screen that allows users to add new players to the app.

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 you tap this button we’ll make a new modal screen pop up that lets you enter the details for a new player.

Drag a new Table View 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 with.

Keep the new Table View Controller selected and embed it in a Navigation Controller (in case you forgot, from the menubar pick Editor\Embed In\Navigation Controller).

Here’s the trick: Select the + button that we just added on the Players screen and ctrl-drag to the new Navigation Controller:

Creating a segue in the storyboard editor

Release the mouse button and a small popup menu shows up:

Popup to choose Segue type - push, modal, or custom

Choose Modal. This places a new arrow between the Players screen and the Navigation Controller:

A new segue in the storyboard editor

This type of connection is known as a segue (pronounce: seg-way) and represents a transition from one screen to another. The connections we had so far were relationships and they described one view controller containing another. A segue, on the other hand, changes what is on the screen. They 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 we just did, dragging from the Bar Button Item to the next screen, is enough to create the transition. (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!

App with modal segue

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 we’ll also see “push” segues that push new screens on the navigation stack.

The new screen isn’t very useful yet — you can’t even close it to go back to the main screen!

Segues only go one way, from the Players screen to this new one. To go back, we have to use the delegate pattern. For that, we first have to give this new scene its own class. Add a new UITableViewController subclass to the project and name it PlayerDetailsViewController.

To hook it up to the storyboard, switch back to MainStoryboard.storyboard, select the new Table View Controller scene and 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 we’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 Done.

Setting the title of the navigation bar to "Add Player"

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 we’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 the Storyboard Editor, 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:

Connecting the action of a bar button item to the view controller in the storyboard editor

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 actually 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 that 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.

Don’t forget to synthesize the delegate property:

@synthesize delegate;

Now that we’ve given the PlayerDetailsViewController a delegate protocol, we 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>

And to the end of 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 we’ll make them do more interesting things.

The dismissViewControllerAnimated:completion: method is new in iOS 5. You may have used dismissModalViewControllerAnimated: before. That method still works but the new version is the preferred way to dismiss view controllers from now on (it also gives you the ability to execute additional code after the screen has been dismissed).

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 the Storyboard Editor just by dragging a line between the two. Unfortunately, that is not possible. To pass data to the new view controller during a segue, we still need to write some code.

Add the following method to PlayersViewController (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] 
              objectAtIndex: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 we can use this opportunity to send data to it. (You never call prepareForSegue yourself, it’s a message from UIKit to let you know that a segue has just been triggered.)

Note that the destination of the segue is the Navigation Controller, because that is what we connected to the Bar Button Item. To get the PlayerDetailsViewController instance, we have to dig through the Navigation Controller’s viewControllers property 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 we 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 we’ll do later in this tutorial).

To fix this issue, go into the Storyboard Editor 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”:

Setting the segue identifier

If you 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 this must be done by the delegate. I personally prefer to let the delegate to this but if you want the modal screen to close itself, then go right ahead. There’s one thing you should be aware of: If you previously used [self.parentViewController dismissModalViewControllerAnimated:YES] to close the screen, then that may no longer work. Instead of using self.parentViewController, simply call the method on self or on self.presentingViewController, which is a new property that was introduced with iOS 5.

By the way, the Attributes Inspector for the segue also has a Transition field. You can choose different animations:

Setting the transition style for a segue

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!

We’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:

  1. 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.)
  2. Give the segue a unique Identifier. (It only has to be unique in the source scene; different scenes can use the same identifier.)
  3. Create a delegate protocol for the destination scene.
  4. 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.
  5. Make the source scene implement the delegate protocol. It should dismiss the destination view controller when Cancel or Done is pressed.
  6. Implement prepareForSegue in the source view controller and do destination.delegate = self;.

Delegates are necessary because 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 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.

Contributors

Over 300 content creators. Join our team.