Blog

News, tutorials, and analyses of all things related to enterprise mobile apps

Introduction to Storyboards in iOS 5


Please note, a PDF of this tutorial and the XCode project are available for download at the end of this post.

If you’ve ever programmed for iOS prior to version 5 then you have most certainly used NIB files.

While very powerful and flexible for designing your interfaces, determining how your application flows when working on larger projects was a nightmare. You had too look at your code to see which view controllers were being pushed/presented depending on your user’s actions.

This is no longer the case, with Storyboards you can now design your interface in a single file (or multiple files if you prefer) with little or no code at all!

Let’s work our way through a small tutorial to show just how powerful Storyboards really are. We’re going to build a very simple jog log.

Fire up Xcode (I’ll be using version 4.3) and go to File > New > Project. Select Master-Detail Application from the templates and click Next. Give your project a name, I’ll be using LexJog. Feel free to use a class prefix if you want and make sure that you select iPhone for the Device Family option. Also make sure that only Use Storyboards and Use Automatic Reference Counting are selected and click Next.

Select a location where you want to save your project and click Create. That’s all we need to use Storyboards in our Project. Take a look at the Project Navigator and notice a file called MainStoryboard.storyboard, this is where all of our interface will live now!

When incorporating Storyboards into existing projects all you need to do is create a new Storyboard file from the User Interface templates in Xcode, go to your project settings and specify your Main Storyboard file:

If you are making a Universal application you will have to specify a Storyboard file for iPhone and iPad (also located in the Project Settings).

Whilst inside the Project Settings make sure you only select Portrait for the Supported Device Orientations.

Xcode has created Master and Detail view controller subclasses for us. Because we want to do all of this from scratch in order to get a better understanding of this, go ahead and delete both of them (Be sure to select Move To Trash or you will keep the files inside your project folder).

Also, If you open up the MainStoryboard file you will notice the template has created some initial view controllers but go ahead and delete everything in there as well.

Now, the jog log is going to be a simple table view with custom cells that will show the date, miles and calories of a jog. When we select a row in the table we will be presented with a view of the jog’s info as well as notes for that session.

With this you’ll get a better understanding how to work with storyboards, create custom cells, present view controllers, use segues (I’ll explain this as we work through the tutorial) and how much code can be saved when using Storyboards.

In your MainStoryboard file you should have nothing in the canvas. Go to the Object Library drag a Navigation Controller to the canvas, you should now see the following:

Notice the arrow on the left side of the Navigation Controller, this indicates the initial view controller.

Take a look at the Document Outline on the left, you will see that there’s no longer a Window property or an AppDelegate object created for us. This is because the AppDelegate now inherits from UIResponder and the window’s rootViewController property is assigned to the view controller in our Storyboard that has the arrow coming in from the left.

The downside to this is that we can no longer create an IBOutlet to connect a view controller to the app delegate inside Interface Builder, but this can all be done via code now without much difficulty.

Our interface is looking pretty neat already, but what’s this arrow with a circular symbol pointing from the navigation controller to the table view controller? This is called a Relationship.

Previously when using NIBs you would have to set the subclass of your navigation controller’s root view controller and then tell it what NIB it was loaded from. With Storyboards we are establishing this parent-child relationship via a connection.

Right click on the Navigation Controller in the Document Outline and take a look at what it says:

The relationship setting the table view controller as the navigation controller’s root view controller is now a simple connection, similar to an outlet or action connection from a button or UI element.

If you have a keen eye and are quite curious, you will notice that the relationship is under a category called Storyboard Segues. Segues (pronounced seg-ways) are new to iOS 5 and Storyboards and enable you to define how to present a view controller on the screen.

I say present because, coming from prior versions of iOS, you might be used to either pushing a view controller onto the navigation stack or presenting it modally. Now, with Storyboards, we simply present a view controller, how this is done is all up to you. The options are:

  • Modal
  • Push
  • Custom

Push is used just as it always has, to push a new view controller onto the navigation stack when using a navigation controller. There are no options for this as it’s always presented with a right to left animation.

Modal is used to present a view controller modally, you see these often in applications when showing an about page, a help page, app settings or something similar that’s not really part of your application’s navigation flow.

The cool thing about this segue is that there are now several animation options to choose from: Cover Vertical, Flip Horizontal, Cross Dissolve, Partial Curl.

Custom is, as the name implies, a way for you to define your own animation and transition from one view controller to the next. This is out of the scope of this tutorial but it’s incredibly easy to do with very little code and effort.

Relationship is not really a segue in the sense that no animation takes place, it just establishes a connection between a container view controller and a child view controller.

In our case we have set a parent-child relationship from the navigation controller (container) to the table view controller (child & root view controller of the navigation controller).

About half of our interface is ready, now we need to create a custom cell and add the detail view controller, let’s start with the latter. Drag a view controller from the Object Library to the canvas, right click on the table view cell (either in the canvas or the Document Outline) and select the Push segue.

We didn’t click from the table view controller but rather from the table cell. That’s because we want to push the new view controller when a row in the table is selected. How cool is that?

This can be done for any UI element that has an action (buttons, switches, gestures, etc). You no longer have to go to your table view delegate and manually push a view controller inside the didSelectRowAtIndexPath: method or write IBAction methods just to transition between view controllers.

Storyboards are so smart that our cell now has a disclosure indicator and the view controller we are pushing onto the stack has a simulated navigation bar (because it knows it’s now part of a Navigation Controller’s hierarchy).

Before we build and run I’ll introduce you to another new feature of Storyboards, static cells. Up until now when creating tables you always have to define a data source and provide info for the cells via code.

But what if your table will always have the same amount of cells? Like for example when creating a selection table with the same elements or user info input. Wouldn’t it be nice to simple do this in Interface Builder rather than having to write all of this boilerplate code that will work exactly the same every time?

Static cells are the solution to this, not only can you rapidly prototype an app or create your interface in Interface Builder, you save a lot of time and prevent errors by having to write less code.

Select the table view and go to the Attributes Inspector. Change the Content from Dynamic Prototypes to Static Cells:

Now you can simply drag cells to the table view and add as many as you like, each one can even have a different style if necessary. I’ve made 3 cells with the Basic style and given them a title for us to test:

Once you create your own static cells go ahead and Build & Run your project. Click on the first row and you should see the detail view controller being pushed onto the stack.

Right now if you click on your other static cells, nothing will happen, this is because each static cell can have a different view controller presented via a segue. This will not happen when we work with dynamic prototypes because all cells will use the same segue.

Some of the other settings you can change in Storyboards are what happens when you select a cell (if you want the highlight to fade out or not), the color of the table view background, row separators and table style and also define each view controller’s title.

I’ve changed the title of the Root View Controller to LexJog and the detail view controller to Jog Session:

We haven’t created any subclasses or written any code and we already have:

  • A navigation hierarchy
  • View Controller titles
  • Cells with static content
  • Segues and relationships
  • Events being handled when a user selects a row

Prior to Storyboards and iOS 5, most of this had to be done in code, another great reason to use Storyboards.

We don’t want the static cells, we want our own custom cells. Once again select the table view and in the Attributes Inspector change the Content to Dynamic Prototypes and use only 1 Prototype Cell. Select the cell and change it’s style to custom and write JogCell for the reuse identifier.

The reuse identifier is necessary because it will be used when dequeuing a reusable cell. Before Storyboards you had to create a NIB for your cell and write a lot of code to load it. Now you simply pass it the reuse identifier and iOS will either create a new cell from your Storyboard or dequeue an existing one at runtime when possible.

Less code and optimized performance for free, not bad at all Mr. Storyboard.

For the custom jog cell I’ve gone ahead and made it’s height larger and added some labels. Don’t forget to select the table view and, in the Attributes Inspector, change the row height to the height of your custom cell.

Looking good so far. In the detail view controller I’ve added the same labels as well as a text view for the session’s notes:

Great, our interface is ready. All that’s left to do is create some subclasses and outlets, make the connections and we’re good to go. Make sure everything compiles and runs properly before moving on, otherwise go back and check what’s missing or where things went wrong for you.

In the Project Navigator right click on the LexJog folder and select New File. Create an Objective-C Class file from the Cocoa Touch template list and make it a subclass of UITableViewCell called JogCell. Delete all of the code in the JogCell.m file for now.

Create 2 more subclasses, one UITableViewController subclass called JogListViewController and a UIViewController subclass called JogDetailViewController.

Go back to your MainStoryboard file, select your custom cell and in the Identity Inspector set its class to JogCell from the drop down available. Set the table view controller’s class to JogListViewController and the detail view controller’s class to JogDetailViewController.

Before we can connect our labels to our custom class we need to create some IBOutlets. You can do this with the Assistant Editor or via code. Whichever you choose is fine, just make sure you have the following code in your JogCell.h file:

#import

@interface JogCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UILabel *caloriesLabel;
@property (weak, nonatomic) IBOutlet UILabel *dateLabel;
@property (weak, nonatomic) IBOutlet UILabel *milesLabel;

@end

We declare our label outlet properties as weak because they are subviews of the cell and are already strongly retained by it. No need to retain it twice by declaring our properties to strong. For more information about this be sure to read Apple’s ARC Documentation.

Now in the JogCell.m file simple synthesize your properties as follows:

#import “JogCell.h”

@implementation JogCell

@synthesize caloriesLabel = _caloriesLabel;
@synthesize dateLabel = _dateLabel;
@synthesize milesLabel = _milesLabel;

@end

Once again go back to the Storyboard file and connect each label to it’s corresponding outlet by right clicking on the cell (either in the canvas or via the Document Outline):

We have to do the same for our detail view controller so go ahead and add the following code to the JogDetailViewController.h file:

#import

@interface JogDetailViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *caloriesLabel;
@property (weak, nonatomic) IBOutlet UILabel *dateLabel;
@property (weak, nonatomic) IBOutlet UILabel *milesLabel;
@property (weak, nonatomic) IBOutlet UITextView *notesTextView;
@property (strong, nonatomic) NSDictionary *jogDictionary;

@end

And then the proper synthesize statements in the JogDetailViewController.m file (I’ve also cleaned up the file for unnecessary method stubs):

@implementation JogDetailViewController

@synthesize caloriesLabel = _caloriesLabel;
@synthesize dateLabel = _dateLabel;
@synthesize milesLabel = _milesLabel;
@synthesize notesTextView = _notesTextView;
@synthesize jogDictionary = _jogDictionary;

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

@end

Go ahead and connect your labels and the text view in the Storyboard and come back when done :)

We’ve added an NSDictionary property to store the information for the jog session we are presenting in the detail view controller, this way we can use that info to set our label and textview’s text.

Back in the JogListViewController.m file cleanup the code so it looks as follows:

#import “JogListViewController.h”

@interface JogListViewController ()

@end

@implementation JogListViewController

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

#pragma mark – Table View Data Source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @”Cell”;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

// Configure the cell…

return cell;
}

@end

The header file doesn’t need any custom code and, once again, all of the unnecessary method stubs have been removed. A class extension has been added at the top of the implementation file to hold an array of dictionaries with our jog log. Let’s take care of that first:

@interface JogListViewController ()
@property (strong, nonatomic) NSArray *jogArray;
@end

Be sure to synthesize the property inside the class implementation:

@implementation JogListViewController

@synthesize jogArray = _jogArray;

… // View controller code here

@end

Now override the jogArray property getter so we lazy load it with some static jog data. The code for this should be nothing new to you and is just a collection of dictionary objects with strings for a jog’s session info:

- (NSArray *)jogArray
{
if (_jogArray)
{
return _jogArray;
}

NSDictionary *jog1 = [NSDictionary dictionaryWithObjectsAndKeys:@"10", @"miles",
@"March 5, 2012", @"date",
@"980", @"calories",
@"This was my first ever jog", @"notes", nil];

NSDictionary *jog2 = [NSDictionary dictionaryWithObjectsAndKeys:@"2", @"miles",
@"March 7, 2012", @"date",
@"100", @"calories",
@"This was my shortest jog so far :(", @"notes", nil];

NSDictionary *jog3 = [NSDictionary dictionaryWithObjectsAndKeys:@"5", @"miles",
@"March 9, 2012", @"date",
@"460", @"calories",
@"I'm trying to make 5 miles my daily average", @"notes", nil];

NSDictionary *jog4 = [NSDictionary dictionaryWithObjectsAndKeys:@"4.9", @"miles",
@"March 11, 2012", @"date",
@"450", @"calories",
@"Very close to my daily average", @"notes", nil];

_jogArray = [NSArray arrayWithObjects:jog1, jog2, jog3, jog4, nil];

return _jogArray;
}

With this you also need to update the numberOfRowsInSectionMethod: so it uses the amount of jog sessions stored in the local jogArray property:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.jogArray.count;
}

We now have to implement the cellForRowAtIndexPath: method so we populate the labels with a jog session’s info. Add an import for the jog cell at the top of the file:

#import “JogCell.h”

And then update the cellForRowAtIndexPath: method as follows:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @”JogCell”;

JogCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

NSDictionary *jog = [self.jogArray objectAtIndex:indexPath.row];

cell.caloriesLabel.text = [jog objectForKey:@"calories"];
cell.dateLabel.text = [jog objectForKey:@"date"];
cell.milesLabel.text = [jog objectForKey:@"miles"];

return cell;
}

A few things to notice here. First we are using the same cell identifier as the one setup in Interface Builder, this is necessary otherwise the custom jog cell will not be created and you could have your app crash at runtime.

The dequeueReusableCellWithIdentifier: method is then called on the table view and the returned UITableViewCell is stored in a local variable of type JogCell.

With previous versions of iOS, and when using NIBs, you had to ask for a reusable cell, check if it was given to you or otherwise manually load the cell from the NIB before populating the cell with data and custom info.

Now with Storyboards you just ask for a reusable cell and let iOS handle everything else, even if the cell has to be created you don’t have to worry about that. Less code, fewer errors!

Go ahead and Build & Run your project to see how things are looking:

Yay! We barely added any code and we already have the table view showing our jog sessions complete with custom cells and all. Feel free to add more jog dictionary objects so you can see that the table scrolls correctly.

You can also go ahead and add code to let the user manually add a jog session and persist that information after the app closes. This is left as an exercise for you :)

If you select a cell you can see that the detail view controller is pushed onto the navigation stack albeit without any of the session’s info.

Since we are no longer pushing a view controller via code, we need to have a way to pass it info or set it up before it’s presented. Luckily Apple has us covered on this with a new method we can override:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;

This method let’s us prepare for any incoming segues. The UIStoryboardSegue object received as a parameter has a property called destinationViewController that can be used to pass info to the view controller we are presenting. Here’s the code to do that:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSDictionary *jog = [self.jogArray objectAtIndex:[self.tableView indexPathForSelectedRow].row];

JogDetailViewController *jogDetailViewController = segue.destinationViewController;
jogDetailViewController.jogDictionary = jog;
}

Don’t forget to import the JogDetailViewController.h header so this code works:

#import “JogDetailViewController.h”

Finally, in the JogDetailViewController.m file be sure to update the viewDidLoad code so we populate the labels and text view with the jog session info:

- (void)viewDidLoad
{
[super viewDidLoad];

self.caloriesLabel.text = [self.jogDictionary objectForKey:@"calories"];
self.dateLabel.text = [self.jogDictionary objectForKey:@"date"];
self.milesLabel.text = [self.jogDictionary objectForKey:@"miles"];
self.notesTextView.text = [self.jogDictionary objectForKey:@"notes"];
}

Build & Run your code and….VOILA! Everything is working:

We barely added any code and have a fully working app prototype that can easily be expanded into a full fledged product. Let’s be honest, the lengthiest code was the jogArray custom setter to populate it with test info.

Storyboards have allowed us to save an enormous amount of time and code and has translated a lot of that to Interface Builder and attributes of interface elements.

There’s a lot we didn’t cover here but hopefully this is a good starting point for you. Form here on you can try the following:

  • Different segues types
  • Create a custom segue
  • Present a view controller programmaticaly
  • Create a more robust user interface with Storyboards
  • Mix Storyboards with NIBs

I hope you’ve enjoyed this and be sure to check out our blog for more tips and tutorials in the future. :)

Felipe
Lextech iOS Engineer

Get a PDF of this tutorial here: LexJog Tutorial

Get the XCode project here: LexJog

zp8497586rq

There are 2 comments .

Miguel Bayona —

Nice tutorial. However, do you have a full version that is not purely static? It would be awesome to be able to add entries to the jog log and its corresponding set of jog sessions with comments … I guess this is not too far from where you left. Very nice demo, and a terrific way to demonstrate the use of the view controllers…

Miguel

    Felipe Laso Marsetti —

    Hey Miguel,

    Thank you very much for your comments :)

    The demo is indeed using static data. In order to use dynamic data you’d have to allow the table view to be editable, meaning inserting/deleting rows (you can even allow re-ordering of rows). You may want to look at using an NSFetchedResultsController which automatically manages all of this stuff so long as you use Core Data. If you don’t want that. however, it’s still possible to do it manually :)

    At the moment I cannot update the tutorial. I can, however, give you several awesome resources to help you continue with expanding the table functionality as well as my Twitter handle so you can message me and ask for any code specific questions if you need to :)

    First, here’s my Twitter: @Airjordan12345

    And for resources here are some very good ones:

    How To Use NSFetchedResultsController
    Getting Started with Core Date on iOS 5
    http://www.raywenderlich.com/1797/how-to-create-a-simple-iphone-app-tutorial-part-1

    Again I thank you for reading and your comments, and feel free to ask more questions here or on Twitter, ask for code level support, more resources or questions about the ones I sent :)

    Have a good week,
    Feli

Comments are closed

Copyright ©2013 Lextech Global Services. All Rights Reserved.