Storyboards Tutorial in iOS 7: Part 1

Matthijs Hollemans

35_sb_square_image

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!

Storyboarding is an exciting feature first introduced with iOS 5 that saves you a lot of time building user interfaces for your apps.

To show you what a storyboard is, I’ll let a picture do the talking. This is the storyboard that you will be building in this tutorial:

The full storyboard for the app

You may not know exactly yet what the app does but you can clearly see which screens it has and how they are related. That is the power of using storyboards.

If you have an app with many different screens then storyboards can help reduce the amount of glue code you have to write to go from one screen to the next. Instead of using a separate nib file for each view controller, your app uses a single storyboard that contains the designs of all of these view controllers and the relationships between them.

Storyboards have a number of advantages over regular nibs:

  • With a storyboard you have a better conceptual overview of all the screens in your app and the connections between them. It’s easier to keep track of everything because the entire design is in a single file, rather than spread out over many separate nibs.
  • The storyboard describes the transitions between the various screens. These transitions are called “segues” and you create them by simply ctrl-dragging from one view controller to the next. Thanks to segues you need less code to take care of your UI.
  • Storyboards make working with table views a lot easier with the new prototype cells and static cells features. You can design your table views almost completely in the storyboard editor, something else that cuts down on the amount of code you have to write.

Not everything is perfect, of course, and storyboards do have some limitations. The storyboard version of Interface Builder isn’t as powerful as the old nib editor and there are a few handy things nibs can do that storyboards unfortunately can’t. You also need a big monitor, especially when you write iPad apps!

If you’re the type who hates Interface Builder and who really wants to create his entire UI programmatically, then storyboards are probably not for you. Personally, I prefer to write as little code as possible — especially UI code! — so this tool is a welcome addition to my arsenal.

And if you want to keep using nibs then go right ahead, but know that you can combine storyboards with nibs. It’s not an either-or situation.

In this tutorial you’ll take a look at what you can do with storyboards. You’re going to build a simple app that lets you create a list of players and games, and rate their skill levels. In the process, you’ll learn the most common tasks that you’ll be using storyboards for.

Storyboards tutorial: iOS 7 style

Fire up Xcode and create a new project. You’ll use the Single View Application template as the starting point and then build up the app from there.

01_sb_newproject

Fill in the template options as follows:

  • Product Name: Ratings
  • Organization Name: fill this in however you like
  • Company Identifier: the identifier that you use for your apps, in reverse domain notation
  • Class Prefix: leave this empty
  • Devices: iPhone

With previous versions of Xcode you had to specifically choose to use storyboards in the new project, but as of Xcode 5 this no longer an option; storyboards are enabled by default.

After Xcode has created the project, the main Xcode window looks like this:

New project in Xcode

The new project consists of two classes, AppDelegate and ViewController, and the star of this tutorial: the Main.storyboard file. Notice that there are no .xib files in the project.

This is a portrait-only app, so before you continue, uncheck the Landscape Left and Landscape Right options under Deployment Info, Device Orientation.

Let’s take a look at that storyboard. Click Main.storyboard in the list of files to open it in Interface Builder:

The initial storyboard

Editing storyboards in Interface Builder works pretty much the same way as editing nibs. You can drag new controls from the Object Library (see bottom-right corner) into your view controller to design its layout. The difference is that the storyboard doesn’t contain just one view controller from your app, but all of them.

The official storyboard terminology for a view controller is “scene”, but you can use the terms interchangeably. The scene is what represents the view controller in the storyboard. Previously you would use a separate nib for each scene / view controller, but now they are all combined into a single storyboard.

On the iPhone only one of these scenes is visible at a time, but on the iPad you can show several at once, for example the master and detail panes in a split-view, or the content of a popover.

Note: Xcode 5 enables Auto Layout by default for storyboard and nib files. Auto Layout is a cool new technology for making flexible user interfaces that can easily resize, which is useful on the iPad and for supporting the larger iPhone 5, but it only works on iOS 6 and up. It also has a bit of a learning curve, which is why you’re not using it in this tutorial. To learn more about Auto Layout, see our books iOS 6 by Tutorials and iOS 7 by Tutorials.

Disable Auto Layout from the File inspector for the storyboard:

Disabling auto layout

To get some feel for how the storyboard editor works, drag some controls into the blank view controller:

Dragging controls into storyboard

Find this button at the bottom of the storyboard canvas:

The document outline button

Click it to open the Document Outline in the sidebar on the left:

Document outline

When editing a nib this area lists just the components from that one nib, but for a storyboard it shows the contents of all your view controllers. Currently there is only one view controller (or scene) in your storyboard but in the course of this tutorial you’ll be adding several others.

There is a miniature version of this Document Outline below the scene, named the Dock:

Dock

The Dock shows the top-level objects in the scene. Each scene has at least a View Controller object, a First Responder object, and an Exit item, but it can potentially have other top-level objects as well. The Dock is convenient for making connections to outlets and actions. If you need to connect something to the view controller, you can simply drag to its icon in the Dock.

Note: You probably won’t be using the First Responder very much. This is a proxy object that refers to whatever object has first responder status at any given time. It was also present in your nibs and you probably never had a need to use it then either. As an example, you can hook up the Touch Up Inside event from a button to First Responder’s cut: selector. If at some point a text field has input focus then you can press that button to make the text field, which is now the first responder, cut its text to the pasteboard.

Run the app and it should look exactly like what you designed in the editor (shown here on the 4-inch Retina iPhone simulator running iOS 7.0):

First app in the simulator

If you’ve ever made a nib-based app before then you always had a MainWindow.xib file. This nib contained the top-level UIWindow object, a reference to the App Delegate, and one or more view controllers. When you put your app’s UI in a storyboard, however, MainWindow.xib is no longer used. So how does the storyboard get loaded by the app?

Let’s take a peek at the application delegate. Open up AppDelegate.h and you’ll see it looks like this:

#import <UIKit/UIKit.h>
 
@interface AppDelegate : UIResponder <UIApplicationDelegate>
 
@property (strong, nonatomic) UIWindow *window;
 
@end

It is a requirement for using storyboards that your application delegate inherits from UIResponder and that it has a UIWindow property. If you look into AppDelegate.m, you’ll see that it does absolutely nothing. All the methods are practically empty. Even application:didFinishLaunchingWithOptions: simply returns YES.

The secret is in the Ratings-Info.plist file. Click on Ratings-Info.plist (you can find it in the Supporting Files group) and you’ll see this:

09_sb_infoplist

Storyboard apps use the key UIMainStoryboardFile, or “Main storyboard file base name”, to specify the name of the storyboard that must be loaded when the app starts. When this setting is present, UIApplication will load the named storyboard file and automatically instantiates the first view controller from that storyboard, then puts its view into a new UIWindow object. No programming necessary.

You can also see this in the Project Settings screen in the Deployment Info section:

10_sb_targetsummary

For the sake of completeness, also open main.m to see what’s in there:

#import <UIKit/UIKit.h>
 
#import "AppDelegate.h"
 
int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

The app delegate is not part of the storyboard. You have to pass the name of your app delegate class to UIApplicationMain(), otherwise it won’t be able to find it.

Just Add It To My Tab

The Ratings app has a tabbed interface with two screens. With a storyboard it is really easy to create tabs.

Switch back to Main.storyboard, and drag a Tab Bar Controller from the Object Library into the canvas. You may want to maximize your Xcode window first, because the Tab Bar Controller comes with two view controllers attached and you’ll need some room to maneuver. You can zoom out using the little floating panel in the bottom-right corner of the canvas.

Tab bar controller

The new Tab Bar Controller comes pre-configured with two other view controllers, one for each tab. UITabBarController is a so-called container view controller because it contains one or more other view controllers. Two other common containers are the Navigation Controller and the Split View Controller (you’ll see both of them later).

The container relationship is represented by the arrows between the Tab Bar Controller and the view controllers that it contains.

12_sb_containment

Note: If you want to move the Tab Bar Controller and its attached view controllers as a group, you can ⌘-click to select multiple scenes and then move them around together. (Selected scenes have a thick blue outline.)

Drag a label into the first view controller and give it the text “First Tab”. Also drag a label into the second view controller and name it “Second Tab”. This allows you to actually see something happen when you switch between the tabs.

Note: You can’t drag stuff into the scenes when the editor is zoomed out. You’ll need to return to the normal zoom level first. You can do that quickly by double-clicking in the canvas.

Select the Tab Bar Controller and go to the Attributes inspector. Check the box that says Is Initial View Controller.

13_sb_initialcontroller

In the canvas the arrow that at first pointed to the regular view controller now points at the Tab Bar Controller:

13_sb_initialcontroller2

This means that when you run the app, UIApplication will make the Tab Bar Controller the main screen. The storyboard always has a single view controller that is designated the initial view controller, that serves as the entry point into the storyboard.

Tip: To change the initial view controller, you can also drag the arrow between view controllers.

Run the app and try it out. The app now has a tab bar and you can switch between the two view controllers with the tabs:

App with tabs

Xcode actually comes with a template for building a tabbed app (unsurprisingly called the Tabbed Application template) that you could have used, but it’s good to know how this works so you can also create a Tab Bar Controller by hand if you have to.

Note: If you connect more than five scenes to the Tab Bar Controller, it automatically gets a More… tab when you run the app. Pretty neat!

Remove the view controller that was originally added by the template, as you’ll no longer be using it. The storyboard now contains just the tab bar and the two scenes for its tabs.

Adding a Table View Controller

The two scenes that are currently attached to the Tab Bar Controller are both regular UIViewControllers. You are going to replace the scene from the first tab with a UITableViewController instead.

Click on that first view controller to select it, and then delete it. From the Object Library drag a new Table View Controller into the canvas in the place where that previous scene used to be:

15_sb_tablecontroller

With the Table View Controller selected, choose Editor\Embed In\Navigation Controller from Xcode’s menubar. This adds yet another view controller to the canvas:

15_sb_navcontroller

You could also have dragged in a Navigation Controller from the Object Library, but this Embed In command is just as easy.

Because the Navigation Controller is also a container view controller (just like the Tab Bar Controller), it has a relationship arrow pointing at the Table View Controller. You can also see these relationships in the Document Outline:

16_sb_do_relations

Notice that embedding the Table View Controller gave it a navigation bar. Interface Builder automatically put it there because this scene will now be displayed inside the Navigation Controller’s frame. It’s not a real UINavigationBar object but a simulated one.

If you look at the Attributes inspector for the Table View Controller, you’ll see the Simulated Metrics section at the top:

17_sb_simmetrics

“Inferred” is the default setting for storyboards and it means the scene will show a navigation bar when it’s inside of a Navigation Controller, a tab bar when it’s inside of a Tab Bar Controller, and so on. You could override these settings if you wanted to, but keep in mind they are here only to help you design your screens. The Simulated Metrics aren’t used during runtime; they’re just a visual design aid that shows what your screen will end up looking like.

Let’s connect these two new scenes to the Tab Bar Controller. Ctrl-drag from the Tab Bar Controller to the Navigation Controller:

18_sb_dragtonavcontroller

When you let go, a small popup menu appears:

19_sb_createrel

Choose the Relationship Segue – view controllers option. This creates a new relationship arrow between the two scenes:

19_sb_createrel_2

The Tab Bar Controller has two such relationships, one for each tab. The Navigation Controller itself has a relationship connection to the Table View Controller. There is also another type of arrow, the segue, that we’ll talk about later.

When you made this new connection, a new tab was added to the Tab Bar Controller, simply named “Item”. For this app, you want this new scene to be the first tab, so drag the tabs around to change their order:

Drag tab items

Run the app and try it out. The first tab now contains a table view inside a navigation controller.

First tab with table view

Before you put some actual functionality into this app, let’s clean up the storyboard a little. You will name the first tab “Players” and the second “Gestures”. Unlike what you may expect, you do not change this on the Tab Bar Controller itself, but in the view controllers that are connected to these tabs.

As soon as you connect a view controller to the Tab Bar Controller, it is given a Tab Bar Item object. You use the Tab Bar Item to configure the tab’s title and image.

Select the Tab Bar Item inside the Navigation Controller, and in the Attributes inspector set its Title to Players:

22_sb_renametab

Rename the Tab Bar Item for the view controller from the second tab to Gestures.

A well-designed app should also put some pictures on these tabs. The resources for this tutorial contains a subfolder named Images. Add that folder to the project. In the Attributes inspector for the Players Tab Bar Item, choose the Players.png image. You probably guessed it, but give the Gestures item the image Gestures.png.

A view controller that is embedded inside a Navigation Controller has a Navigation Item that is used to configure the navigation bar. Select the Navigation Item for the Table View Controller (you can find it in the Document Outline) and change its title in the Attributes inspector to Players.

Alternatively, you can double-click the navigation bar and change the title there. (Note: you should double-click the simulated navigation bar in the Table View Controller, not the actual Navigation Bar object in the Navigation Controller.)

23_sb_edittitle

Run the app and marvel at your pretty tab bar, all without writing a single line of code!

App with tab bar images

Prototype cells

Prototype cells allow you to easily design a custom layout for your table view cells directly from within the storyboard editor.

The Table View Controller comes with a blank prototype cell. Click on that cell to select it and in the Attributes inspector set the Style option to Subtitle. This immediately changes the appearance of the cell to include two labels.

If you’ve used table views before and created your own cells by hand, you may recognize this as the UITableViewCellStyleSubtitle style. With prototype cells you can either pick one of the built-in cell styles as you just did, or create your own custom design (which you’ll do shortly).

Set the Accessory attribute to Disclosure Indicator and in the Identifier field type PlayerCell. All prototype cells are still regular UITableViewCell objects and therefore should have a reuse identifier.

25_sb_cellsetup

Run the app, and… nothing has changed. That’s not so strange: you still have to make a data source for the table so it will know what rows to display.

Add a new file to the project. Choose the Objective-C class template. Name the class PlayersViewController and make it a subclass of UITableViewController. The With XIB for user interface option should be unchecked because you already have the design of this view controller in the storyboard. No nibs today!

26_sb_createtblcontrollerclass

Go back to the storyboard and select the Table View Controller (make sure you select the actual view controller and not one of the views inside it). In the Identity inspector, set its Class to PlayersViewController. That is the essential step for hooking up a scene from the storyboard with your own view controller subclass. Don’t forget this or your class won’t be used!

Custom class

From now on when you run the app that table view controller from the storyboard is an instance of the PlayersViewController class.

Add a mutable array property to PlayersViewController.h:

#import <UIKit/UIKit.h>
 
@interface PlayersViewController : UITableViewController
 
@property (nonatomic, strong) NSMutableArray *players;
 
@end

This array will contain the main data model for the app, an array that contains Player objects. Add a new file to the project using the Objective-C class template. Name it Player, subclass of NSObject.

Change Player.h to the following:

@interface Player : NSObject
 
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *game;
@property (nonatomic, assign) int rating;
 
@end

There’s nothing special going on here. Player is simply a container object for these three properties: the name of the player, the game he’s playing, and a rating of 1 to 5 stars.

You’ll make the array and some test Player objects in the App Delegate and then assign it to the PlayersViewController’s players property.

In AppDelegate.m, add an #import for the Player and PlayersViewController classes at the top of the file, and add a new instance variable named _players:

#import "AppDelegate.h"
#import "Player.h"
#import "PlayersViewController.h"
 
@implementation AppDelegate 
{
    NSMutableArray *_players;
}
 
// Rest of file...

Then change the application:didFinishLaunchingWithOptions: method to:

 
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    _players = [NSMutableArray arrayWithCapacity:20];
 
    Player *player = [[Player alloc] init];
    player.name = @"Bill Evans";
    player.game = @"Tic-Tac-Toe";
    player.rating = 4;
    [_players addObject:player];
 
    player = [[Player alloc] init];
    player.name = @"Oscar Peterson";
    player.game = @"Spin the Bottle";
    player.rating = 5;
    [_players addObject:player];
 
    player = [[Player alloc] init];
    player.name = @"Dave Brubeck";
    player.game = @"Texas Hold’em Poker";
    player.rating = 2;
    [_players addObject:player];
 
    UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
    UINavigationController *navigationController = [tabBarController viewControllers][0];
    PlayersViewController *playersViewController = [navigationController viewControllers][0];
    playersViewController.players = _players;
 
    return YES;
}

This simply creates some Player objects and adds them to the _players array. But then it does the following:

UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
UINavigationController *navigationController = [tabBarController viewControllers][0];
PlayersViewController *playersViewController = [navigationController viewControllers][0];
playersViewController.players = _players;

Yikes, what is that?! You want to assign the _players array to the players property of PlayersViewController so it can use this array for its data source. But the app delegate doesn’t know anything about PlayersViewController yet, so it will have to dig through the storyboard to find it.

Note: This is one of the limitations of storyboards. With nibs you always had a reference to the App Delegate in your MainWindow.xib and you could make connections from your top-level view controllers to outlets on the App Delegate. That is currently not possible with storyboards. You cannot make references to the app delegate from your top-level view controllers. That’s unfortunate, but you can always get those references programmatically, which is what you do here.

Let’s take it step-by-step:

UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;

You know that the storyboard’s initial view controller is a Tab Bar Controller, so you can look up the window’s rootViewController and cast it to UITabBarController.

The PlayersViewController sits inside a navigation controller in the first tab, so you first look up that UINavigationController object,

UINavigationController *navigationController = [tabBarController viewControllers][0];

and then ask it for its root view controller, which is the PlayersViewController that you are looking for:

PlayersViewController *playersViewController = [navigationController viewControllers][0];

It takes a bit of effort to dig through the storyboard to get the view controller you want, but that’s the way to do it. Of course, if you change the order of the tabs, or change the app so that it no longer has a Tab Bar Controller at the root, then you will have to revise this logic as well.

Now that you have an array full of Player objects, you can continue building the data source for PlayersViewController. Open up PlayersViewController.m and add an import at the top:

#import "Player.h"

Change the table view data source methods to the following:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.players count];
}

The real work happens in cellForRowAtIndexPath. Previously, this method typically looked something like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:CellIdentifier];
    }
 
    // Configure the cell...
    return cell;
}

You would ask the table view to dequeue a cell and if that returned nil because there were no free cells to reuse, you would create a new instance of the cell class. That is no doubt how you’ve been writing your own table view code all this time. Well, no longer!

Replace that method with:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PlayerCell"];
 
    Player *player = (self.players)[indexPath.row];
    cell.textLabel.text = player.name;
    cell.detailTextLabel.text = player.game;
 
    return cell;
}

That looks a lot simpler! The only thing you need to do to get a new cell is:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PlayerCell"];

If there is no existing cell that can be recycled, this will automatically make a new copy of the prototype cell and return it to you. All you need to do is supply the re-use identifier that you set on the prototype cell in the storyboard editor, in this case PlayerCell. Don’t forget to set that identifier, or this little scheme won’t work!

Run the app, and lo and behold, the table view has players in it:

App with players

It just takes one line of code to use these newfangled prototype cells. I think that’s just great!

Note: In this app you’re using only one prototype cell but if your table needs to display different kinds of cells then you can simply add additional prototype cells to the storyboard. You can either duplicate the existing cell to make a new one, or increment the value of the Table View’s Prototype Cells attribute. Be sure to give each cell its own re-use identifier, though.

Designing Your Own Prototype Cells

Using a standard cell style is OK for many apps, but for this app you want to add an image on the right-hand side of the cell that shows the player’s rating (one to five stars). Having an image view in that spot is not supported by the standard cell styles, so you’ll have to make a custom design.

Switch back to Main.storyboard, select the prototype cell in the table view, and set its Style attribute to Custom. The default labels now disappear.

First make the cell a little taller. Either drag its handle at the bottom or change the Row Height value in the Size inspector. Make the cell 55 points high.

Drag two Label objects from the Objects Library into the cell and place them roughly where the standard labels were previously. Just play with the font and colors and pick something you like.

Drag an Image View into the cell and place it on the right, next to the disclosure indicator. Make it 81 points wide; the height isn’t very important. Set its Mode to Center (under View in the Attributes inspector) so that whatever image you put into this view is not stretched.

Make the labels 190 points wide so they don’t overlap with the image view. The final design for the prototype cell looks something like this:

Custom cell design

Because this is a custom designed cell, you can no longer use UITableViewCell’s textLabel and detailTextLabel properties to put text into the labels. These properties refer to labels that aren’t on this cell anymore; they are only valid for the standard cell types. Instead, you will use tags to find the labels.

Give the Name label tag 100, the Game label tag 101, and the Image View tag 102. You can do this in the Attributes inspector.

Then open PlayersViewController.m and add a new method, imageForRating:.

- (UIImage *)imageForRating:(int)rating
{
    switch (rating) {
        case 1: return [UIImage imageNamed:@"1StarSmall"];
        case 2: return [UIImage imageNamed:@"2StarsSmall"];
        case 3: return [UIImage imageNamed:@"3StarsSmall"];
        case 4: return [UIImage imageNamed:@"4StarsSmall"];
        case 5: return [UIImage imageNamed:@"5StarsSmall"];
    }
    return nil;
}

Change tableView:cellForRowAtIndexPath: to the following:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PlayerCell"];
 
    Player *player = (self.players)[indexPath.row];
 
    UILabel *nameLabel = (UILabel *)[cell viewWithTag:100];
    nameLabel.text = player.name;
 
    UILabel *gameLabel = (UILabel *)[cell viewWithTag:101];
    gameLabel.text = player.game;
 
    UIImageView *ratingImageView = (UIImageView *)[cell viewWithTag:102];
    ratingImageView.image = [self imageForRating:player.rating];
 
    return cell;
}

That should do it. Now run the app again. It is possible that the app shows up like this:

Wrong table cell height

Hmm, that doesn’t look quite right, the cells appear to overlap one another. You did change the height of the prototype cell but the table view doesn’t necessarily take that into consideration. There are two ways to fix it: you can change the table view’s Row Height attribute or implement the tableView:heightForRowAtIndexPath: method. The former is much easier, so let’s do that.

Note: You would use heightForRowAtIndexPath if you did not know the height of your cells in advance, or if different rows can have different heights.

Back in Main.storyboard, in the Size inspector of the Table View, set Row Height to 55:

31_sb_cells_rightheight

If you run the app now, it looks a lot better!

App with proper row height

By the way, if you changed the height of the cell by dragging its handle rather than typing in the value, then the table view’s Row Height property was automatically changed too. So it may have worked correctly for you the first time around.

Using a Subclass for the Cell

The table view already works pretty well but I’m not a big fan of using tags to access the labels and other subviews of the prototype cell. It would be much more handy if you could connect these labels to outlets and then use the corresponding properties. As it turns out, you can.

Add a new file to the project, with the Objective-C class template. Name it PlayerCell and make it a subclass of UITableViewCell.

Change PlayerCell.h to:

@interface PlayerCell : UITableViewCell
 
@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
@property (nonatomic, weak) IBOutlet UILabel *gameLabel;
@property (nonatomic, weak) IBOutlet UIImageView *ratingImageView;
 
@end

The class itself doesn’t do much; it just adds properties for nameLabel, gameLabel and ratingImageView, all of which are IBOutlets.

Back in Main.storyboard, select the prototype cell and change its Class to PlayerCell on the Identity inspector. Now whenever you ask the table view for a new cell with dequeueReusableCellWithIdentifier:, it returns a PlayerCell instance instead of a regular UITableViewCell.

Note that you gave this class the same name as the reuse identifier — they’re both called PlayerCell — but that’s only because I like to keep things consistent. The class name and reuse identifier have nothing to do with each other, so you could name them differently if you wanted to.

Now connect the labels and the image view to these outlets. Select the label and drag from New Referencing Outlet in its Connections inspector to the table view cell and select nameLabel and gameLabel, respectively:

32_sb_connectname

Important: You should hook up the controls to the table view cell, not to the view controller! You see, whenever your data source asks the table view for a new cell with dequeueReusableCellWithIdentifier, the table view doesn’t give you the actual prototype cell but a copy (or one of the previous cells is recycled if possible).

This means there will be more than one instance of PlayerCell at any given time. If you were to connect a label from the cell to an outlet on the view controller, then several copies of the label will try to use the same outlet. That’s just asking for trouble. (On the other hand, connecting the prototype cell to actions on the view controller is perfectly fine. You would do that if you had custom buttons or other UIControls on your cell.)

Now that you’ve hooked up the properties, you can simplify the data source code one more time. First import the PlayerCell class in PlayersViewController.m:

#import "PlayerCell.h"

And then change cellForRowAtIndexPath to:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    PlayerCell *cell = (PlayerCell *)[tableView dequeueReusableCellWithIdentifier:@"PlayerCell"];
 
    Player *player = (self.players)[indexPath.row];
    cell.nameLabel.text = player.name;
    cell.gameLabel.text = player.game;
    cell.ratingImageView.image = [self imageForRating:player.rating];
 
    return cell;
}

That’s more like it. You now cast the object that you receive from dequeueReusableCellWithIdentifier to a PlayerCell, and then you can simply use the properties that are wired up to the labels and the image view. Isn’t it great how using prototype cells makes table views a whole lot less messy?

Run the app and try it out. It should still look the same as before, but behind the scenes it’s now using your own table view cell subclass!

Where To Go From Here?

Check out part two of this tutorial, where we’ll cover segues, static table view cells, the Add Player screen, a game picker screen, and the downloadable example project for this tutorial!

This Beginning Storyboards in iOS 7 series is an update preview of one of the chapters in our iOS 5 By Tutorials book. If you like what you see here, check out the book — there’s an entire second chapter on intermediate storyboarding, above and beyond what we’re posting for free here! We also update our all books to the latest versions of iOS so an update of this book for iOS 7 is coming out soon :]

If you felt lost at any point during this tutorial, you also might want to brush up on the basics with my newly updated iOS Apprentice series. In that series, I cover the foundational knowledge you need as an iOS developer from the ground up — perfect for complete beginners, or those looking to fill in some gaps.

If you have any questions or comments on this tutorial or on storyboarding, please join the forum discussion below!

Matthijs Hollemans

Matthijs Hollemans is an independent iOS developer and designer who lives to create awesome software. His focus is on iPad apps because he thinks tablets are way cooler than phones. Whenever he is not glued to his screen, Matthijs plays the piano, is into healthy and natural living, and goes out barefoot running. Visit his website at http://www.hollance.com.

User Comments

50 Comments

[ 1 , 2 , 3 , 4 ]
  • First of all, great tutorial as usual :)

    I've seen that quite a few people have run in to the above exception. Hollance actually gave an answer to this but I just want to clarify a bit, especially as there's a common scenario here. Specifically: check that you are referring to the correct tab bar item in the following code:

    Code: Select all
    UINavigationController *navigationController = [tabBarController viewControllers][0];


    It's very likely that you are referring to the wrong tab bar item - the one that isn't a navigationController. (If you debug you'll see that navigationController at this point is not a UINavigationController but a vanilla UIViewController). You just need to refer to the other item to get things working.

    Code: Select all
    UINavigationController *navigationController = [tabBarController viewControllers][1];


    Simple stuff, but easy enough to miss.

    Hope it helps...
    sinewave440hz
  • I was wondering about implementing state restoration for this tutorial.
    Specifically, how would you restore the relationship between PlayersViewController and
    the UINavigationController that is a modal ("presented") view controller?

    Is it best to use a restoration class or somehow incorporate encodeRestorableStateWithCoder / decodeRestorableStateWithCoder ?

    Thanks in advance,

    and thank you for another awesome tutorial!
    applegal4life
  • Thank you Matthijs for the tutorial on StoryBoards, part 1...now I am off to to part 2.
    JonBoy64
  • Hey I was just wondering how would you pass reference to the appDelegate from the view controller
    like instead of what the tab bar to the navigation bar what if its the tab bar going to a view controller and then to the navigation bar to the playersViewController? What would i have to right in the didFinishLaunchingWithOptions from the appDelegate.h?

    PS Great tutorial btw I found it very helpful when trying to learn storyboard!
    brianx041
  • Great, I am looking something similar but using Swift
    zeeke
[ 1 , 2 , 3 , 4 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in September: iOS 8 App Extensions!

Sign Up - September

RWDevCon Conference?

We are considering having an official raywenderlich.com conference called RWDevCon in DC in early 2015.

The conference would be focused on high quality Swift/iOS 8 technical content, and connecting as a community.

Would this be something you'd be interested in?

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Matt Luedke

... 49 total!

Update Team

Editorial Team

... 23 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • Sonic Zhao

... 33 total!

Subject Matter Experts

  • Richard Casey

... 4 total!