`
dcj3sjt126com
  • 浏览: 1878864 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Storyboards Tutorial in iOS 7: Part 2

    博客分类:
  • IOS
 
阅读更多

原文 http://www.raywenderlich.com/50310/storyboards-tutorial-in-ios-7-part-2

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.

01_sb2_addbutton

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:

02_sb2_addbuttondrag

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:

03_sb2_modalrelation

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 IBActionconnection, then the segue overrides that.)

Run the app and press the + button. A new table view will slide up the screen.

The modal Add Player screen appears

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 thedelegate 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 ofUITableViewController. To hook this new class up to the storyboard, switch back to Main.storyboardand 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).

05_sb2_canceldone

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:

06_sb2_connectdone

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.

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 <em><PlayerDetailsViewControllerDelegate></em>

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 thePlayerDetailsViewController 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.

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!

07_sb2_whyunohide

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:

08_sb2_segid

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.

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

09_sb2_segtrans

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:

  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 — 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.

Static Cells

When you’re finished with this section, the Add Player screen will look like this:

Add Player screen

That’s a grouped table view, of course, but the new thing is that you don’t have to create a data source for this table. You can design it directly in Interface Builder — no need to write cellForRowAtIndexPath for this one. The feature that makes this possible is called static cells.

Select the table view in the Add Player screen and in the Attributes inspector change Content to Static Cells. Change Style from Plain to Grouped and give the table view 2 sections.

10_sb2_staticcells

When you change the value of the Sections attribute, the editor will clone the existing section. (You can also select a specific section in the Document Outline on the left and duplicate it.)

The finished screen will have only one row in each section, so select the superfluous cells and delete them.

Select the top-most section (from the Document Outline). In its Attributes inspector, give the Headerfield the value Player Name.

11_sb2_staticcellsheader

Drag a new Text Field into the cell for this section. Remove its border so you can’t see where the text field begins or ends. Set the Font to System 17 and uncheck Adjust to Fit.

You’re going to make an outlet for this text field on the PlayerDetailsViewController using the Assistant Editor feature of Xcode. Open the Assistant Editor with the button from the toolbar (the one that looks like a tuxedo / alien face). It should automatically open on PlayerDetailsViewController.h (if it doesn’t, use the jumpbar in the right hand split window to select that .h file).

Select the text field and ctrl-drag into the .h file:

12_sb2_dragoutlet

Let go of the mouse button and a popup appears:

Popup for adding an outlet

Name the new outlet nameTextField. After you click Connect, Xcode will add the following property toPlayerDetailsViewController.h:

@property (weak, nonatomic) IBOutlet UITextField *nameTextField;

Creating outlets for views on your table cells is exactly the kind of thing I said you shouldn’t try with prototype cells, but for static cells it is OK. There will be only one instance of each static cell and so it’s perfectly acceptable to connect their subviews to outlets on the view controller.

Set the Style of the static cell in the second section to Right Detail. This gives you a standard cell style to work with. Change the label on the left to read Game and give the cell a Disclosure Indicator accessory.

Make an outlet for the label on the right (the one that says “Detail”) and name it detailLabel. The labels on this cell are just regular UILabel objects. You might need to click few times on the text “Detail” to select the only the label (and not the whole cell) like so:

13_sb2_selecdetaillabel

The final design of the Add Player screen looks like this:

14_sb2_staticcellsfinal

Tip: The screens you have designed so far in this storyboard all have the height of the 4-inch screen of the iPhone 5, which is 568 points tall as opposed to the 480 points of the previous iPhone models. You can toggle between these two form factors using the left-most button from the little floating panel that sits at the bottom of the canvas.

Form factor button

Obviously, your app should work properly with both screen sizes. You can accomplish this with autosizing masks or the new Auto Layout technology from iOS 6. For the Ratings app, you don’t have to do anything fancy. It only uses table view controllers and they automatically resize to fit the extra screen space on the iPhone 5.

Back to the Add Player screen. When you use static cells, your table view controller doesn’t need a data source. Because you used an Xcode template to create the PlayerDetailsViewController class, it still has some placeholder code for the data source and that will prevent the static cells from working properly.

Open PlayerDetailsViewController.m and delete anything from the following line down (except for any code that you added yourself):

#pragma mark - Table view data source

That should silence Xcode about the warnings it has been giving ever since you added this class to the project.

Run the app and check out the new screen with the static cells. All without writing a line of code — in fact, you threw away a bunch of code!

You can’t avoid writing code altogether, though. When you dragged the text field into the first cell, you probably noticed it didn’t fit completely. There is a small margin of space around the text field. The user can’t see where the text field begins or ends, so if they tap in the margin and the keyboard doesn’t appear, they’ll be confused.

To avoid that, you should let a tap anywhere in that row bring up the keyboard. That’s pretty easy to do, just add a tableView:didSelectRowAtIndexPath: method with the following:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 0) {
        [self.nameTextField becomeFirstResponder];
    }
}

This just says that if the user tapped in the first cell, the app should activate the text field. There is only one cell in the section so you only need to test for the section index. Making the text field the first responder will automatically bring up the keyboard. It’s just a little tweak, but one that can save users a bit of frustration.

You should also set the Selection Style for that cell to None (instead of Blue) in the Attributes inspector, otherwise the row appears highlighted if the user taps in the margin around the text field.

All right, that’s the design of the Add Player screen. Now let’s actually make it work.

The Add Player Screen at Work

For now you will ignore the Game row and just let users enter the name of the player.

When the user presses the Cancel button the screen should close and whatever data they entered will be lost. That part already works. The delegate (the Players screen) receives the “did cancel” message and simply dismisses the view controller.

When the user presses Done, however, you should create a new Player object and fill in its properties. Then you should tell the delegate that you’ve added a new player, so it can update its own screen.

Inside PlayerDetailsViewController.m, first add an import:

#import "Player.h"

Then change the done: method to:

- (IBAction)done:(id)sender
{
    Player *player = [[Player alloc] init];
    player.name = self.nameTextField.text;
    player.game = @"Chess";
    player.rating = 1;
    [self.delegate playerDetailsViewController:self didAddPlayer:player];
}

The done: method now creates a new Player instance and sends it to the delegate. The delegate protocol currently doesn’t have this method, so change its definition in PlayerDetailsViewController.hfile to the following:

@class Player;
 
@protocol PlayerDetailsViewControllerDelegate <NSObject>
- (void)playerDetailsViewControllerDidCancel:(PlayerDetailsViewController *)controller;
- (void)playerDetailsViewController:(PlayerDetailsViewController *)controller didAddPlayer:(Player *)player;
@end

The “didSave” method declaration is gone. Instead, there is now a “didAddPlayer”.

The last thing to do is to add the implementation for this method in the object that acts as the delegate,PlayersViewController.m:

- (void)playerDetailsViewController:(PlayerDetailsViewController *)controller didAddPlayer:(Player *)player
{
    [self.players addObject:player];
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:([self.players count] - 1) inSection:0];
    [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self dismissViewControllerAnimated:YES completion:nil];
}

This first adds the new Player object to the array of players. Then it tells the table view that a new row was added (at the bottom), because the table view and its data source must always be in sync.

You could have just done [self.tableView reloadData] but it looks nicer to insert the new row with an animation. UITableViewRowAnimationAutomatic automatically picks the proper animation, depending on where you insert the new row. Very handy.

Try it out, you should now be able to add new players to the list!

App with new players

If you’re wondering about performance of these storyboards, then you should know that loading a whole storyboard at once isn’t a big deal. The Storyboard doesn’t instantiate all the view controllers right away, only the initial view controller. Because your initial view controller is a Tab Bar Controller, the two view controllers that it contains are also loaded (the Players scene from the first tab and the scene from the second tab).

The other view controllers are not instantiated until you segue to them. When you close these view controllers they are immediately deallocated, so only the actively used view controllers are in memory, just as if you were using separate nibs.

Let’s see that in practice. Add these methods to PlayerDetailsViewController.m:

- (id)initWithCoder:(NSCoder *)aDecoder
{
    if ((self = [super initWithCoder:aDecoder])) {
        NSLog(@"init PlayerDetailsViewController");
    }
    return self;
}
 
- (void)dealloc
{
    NSLog(@"dealloc PlayerDetailsViewController");
}

You’re overriding the initWithCoder: and dealloc methods and making them log a message to the Xcode Debug pane. Now run the app again and open the Add Player screen. You should see that this view controller did not get allocated until that point.

When you close the Add Player screen, either by pressing Cancel or Done, you should see the NSLog()from dealloc. If you open the screen again, you should also see the message from initWithCoder: again. This should reassure you that view controllers are loaded on-demand only, just as they would if you were loading nibs by hand.

One more thing about static cells: they only work in UITableViewController. Even though Interface Builder will let you add them to a Table View object inside a regular UIViewController, this won’t work during runtime. The reason for this is that UITableViewController provides some extra magic to take care of the data source for the static cells. Xcode even prevents you from compiling such a project with the error message: “Illegal Configuration: Static table views are only valid when embedded in UITableViewController instances”.

Prototype cells, on the other hand, work just fine in a table view that you place inside a regular view controller. Neither work for nibs, though. At the moment, if you want to use prototype cells or static cells, you’ll have to use a storyboard.

It is not unthinkable that you might want to have a single table view that combines both static cells and regular dynamic cells, but this isn’t very well supported by the SDK. If this is something you need to do in your own app, then see this post on the Apple Developer Forums for a possible solution.

Note: If you’re building a screen that has a lot of static cells — more than can fit in the visible frame — then you can scroll through them in Interface Builder with the scroll gesture on the mouse or trackpad (2 finger swipe). This might not be immediately obvious, but it works quite well.

The Game Picker Screen

Tapping the Game row in the Add Player screen should open a new screen that lets the user pick a game from a list. That means you’ll be adding yet another table view controller, although this time you’re going to push it on the navigation stack rather than show it modally.

Drag a new Table View Controller into the storyboard. Select the Game table view cell in the Add Player screen (be sure to select the entire cell, not one of the labels) and ctrl-drag to the new Table View Controller to create a segue between them. Make this a Push segue (under Selection Segue in the popup, not Accessory Action) and give it the identifier PickGame.

Double-click the navigation bar and name this new scene Choose Game. Set the Style of the prototype cell to Basic, and give it the reuse identifier GameCell. That’s all you need to do for the design of this screen:

16_sb2_pickgame

Add a new file to the project and name it GamePickerViewController, subclass ofUITableViewController. Don’t forget to set the Class in the storyboard so that your newGamePickerViewController object is associated with the Table View Controller.

First let’s give this new screen some data to display. Add a new instance variable toGamePickerViewController.m:

@implementation GamePickerViewController
{
    NSArray *_games;
}

Fill up this array in viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    _games = @[@"Angry Birds",
        @"Chess",
        @"Russian Roulette",
        @"Spin the Bottle",
        @"Texas Hold'em Poker",
        @"Tic-Tac-Toe"];
}

Replace the data source methods from the template with:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [_games count];
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"GameCell"];
    cell.textLabel.text = _games[indexPath.row];
    return cell;
}

That should do it as far as the data source is concerned. Run the app and tap the Game row. The new Choose Game screen will slide into view. Tapping the rows won’t do anything yet, but because this screen is presented on the navigation stack you can always press the back button to return to the Add Player screen.

17_sb2_choosegame

This is pretty cool, huh? You didn’t have to write any code to invoke this new screen. You just ctrl-dragged from the static table view cell to the new scene and that was it.

Of course, this new screen isn’t very useful if it doesn’t send any data back, so you’ll have to add a new delegate for that. Replace GamePickerViewController.h with:

@class GamePickerViewController;
 
@protocol GamePickerViewControllerDelegate <NSObject>
- (void)gamePickerViewController:(GamePickerViewController *)controller didSelectGame:(NSString *)game;
@end
 
@interface GamePickerViewController : UITableViewController
 
@property (nonatomic, weak) id <GamePickerViewControllerDelegate> delegate;
@property (nonatomic, strong) NSString *game;
 
@end

You’ve added a delegate protocol with just one method, and a property that will hold the name of the currently selected game.

Add a new instance variable, _selectedIndex, to GamePickerViewController.m:

@implementation GamePickerViewController
{
    NSArray *_games;
    NSUInteger _selectedIndex;
}

Then add the following line to the bottom of viewDidLoad:

_selectedIndex = [_games indexOfObject:self.game];

The name of the selected game will be set in self.game. Here you figure out what the index is for that game in the list of games. You’ll use that index to set a checkmark in the table view cell. For this to work,self.game must be filled in before the view is loaded. That will be no problem because you will do this in the caller’s prepareForSegue:, which takes place before viewDidLoad.

Still in GamePickerViewController.m, change cellForRowAtIndexPath: to:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"GameCell"];
    cell.textLabel.text = _games[indexPath.row];
 
    if (indexPath.row == _selectedIndex) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
    return cell;
}

This sets a checkmark on the cell that contains the name of the currently selected game. Small gestures such as these will be appreciated by the users of the app.

Replace the placeholder didSelectRowAtIndexPath method from the template with:

#pragma mark - Table view delegate
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
 
    if (_selectedIndex != NSNotFound) {
        UITableViewCell *cell = [tableView cellForRowAtIndexPath:
                                    [NSIndexPath indexPathForRow:_selectedIndex inSection:0]];
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
 
    _selectedIndex = indexPath.row;
 
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    cell.accessoryType = UITableViewCellAccessoryCheckmark;
 
    NSString *game = _games[indexPath.row];
    [self.delegate gamePickerViewController:self didSelectGame:game];
}

First this deselects the row after it was tapped. That makes it fade from the gray highlight color back to the regular white. Then it removes the checkmark from the cell that was previously selected, and puts it on the row that was just tapped. Finally, the method returns the name of the chosen game to the delegate.

Run the app now to test that this works. Tap the name of a game and its row will get a checkmark. Tap the name of another game and the checkmark moves along with it.

Game picker with checkmark

The screen ought to close as soon as you tap a row but that doesn’t happen yet because you haven’t actually hooked up the delegate.

In PlayerDetailsViewController.h, add an import:

#import "GamePickerViewController.h"

And add the delegate protocol to the @interface line:

@interface PlayerDetailsViewController : UITableViewController <GamePickerViewControllerDelegate>

In PlayerDetailsViewController.m, first add a new instance variable:

@implementation PlayerDetailsViewController
{
    NSString *_game;
}

You use this variable to remember the selected game so you can store it in the Player object later. It should get a default value. The initWithCoder: method is a good place for that:

- (id)initWithCoder:(NSCoder *)aDecoder
{
    if ((self = [super initWithCoder:aDecoder])) {
        NSLog(@"init PlayerDetailsViewController");
        _game = @"Chess";
    }
    return self;
}

Note: If you’ve worked with nibs before, then initWithCoder: will be familiar. That part has stayed the same with storyboards; initWithCoder:awakeFromNib, and viewDidLoad are still the methods to use. You can think of a storyboard as a collection of nibs with additional information about the transitions and relationships between them. But the views and view controllers inside the storyboard are still encoded and decoded in the same way.

Change viewDidLoad to display the name of the game in the static table cell:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.detailLabel.text = _game;
}

Now add the prepareForSegue: method to open the game picker screen:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"PickGame"]) {
        GamePickerViewController *gamePickerViewController = segue.destinationViewController;
        gamePickerViewController.delegate = self;
        gamePickerViewController.game = _game;
    }
}

This is similar to what you did before. This time the destination view controller is the game picker screen.prepareForSegue: happens after GamePickerViewController is instantiated but before its view is loaded, so the self.game property already has the game’s name by the time GamePickerViewControllergets to viewDidLoad.

All that remains is to implement the delegate method to close the game picker when the user is done with it:

- (void)gamePickerViewController:(GamePickerViewController *)controller didSelectGame:(NSString *)game
{
    _game = game;
    self.detailLabel.text = _game;
 
    [self.navigationController popViewControllerAnimated:YES];
}

This is pretty straightforward: you put the name of the new game into the _game instance variable and also the cell’s label, and then close the Choose Game screen. Because it’s a push segue, you have to pop this screen off the navigation stack to close it.

The done: method can now put the name of the chosen game into the new Player object, rather than the hardcoded value you’ve been using so far:

- (IBAction)done:(id)sender
{
    Player *player = [[Player alloc] init];
    player.name = self.nameTextField.text;
    player.game = _game;  // only this line is changed
    player.rating = 1;
 
    [self.delegate playerDetailsViewController:self didAddPlayer:player];
}

Awesome. You now have a functioning Choose Game screen!

Marin Danger

Where To Go From Here?

Here is an Ratings iOS 7 example project with all of the code from the above tutorial.

Congratulations, you now know the basics of using the Storyboard Editor, and can create apps with multiple view controllers transitioning between each other with segues!

If you want to learn more about Storyboards, check out our book iOS 5 By Tutorials. It includes another entire chapter on “Intermediate Storyboards” where we cover:

  • How to change the PlayerDetailsViewController so that it can also edit existing Player objects.
  • How to have multiple outgoing segues to other scenes, and how to make your view controllers re-usable so they can handle multiple incoming segues.
  • How to perform segues from disclosure buttons, gestures, and any other event you can think of.
  • How to make custom segues – you don’t have to be limited to the standard Push and Modal animations!
  • How to use storyboards on the iPad, with a split-view controller and popovers.
  • And finally, how to manually load storyboards and use more than one storyboard inside an app.

We do our best to keep our books updated for the latest iOS version, so soon we will be releasing an update for “iOS 5 by Tutorials” that updates the entire book to iOS 7!

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

分享到:
评论

相关推荐

    Programming iOS 12: Dive Deep into Views, View Controllers, and Frameworks

    《Programming iOS 12: Dive Deep into Views, View Controllers, and Frameworks》是一本专为进阶学习者设计的书籍,旨在深入探索苹果iOS 12操作系统中的视图、视图控制器以及各种框架的编程技术。这本书涵盖了iOS...

    iOS Programming: The Big Nerd Ranch Guide (4th Edition) 2014 epub

    Based on Big Nerd Ranch's popular iOS Bootcamp course and its well-tested materials and methodology, this bestselling guide teaches iOS concepts and coding in tandem. The result is instruction that ...

    IOS Storyboard 入门源码

    Storyboard是一项令人兴奋的功能,在iOS5中首次推出,在开发app的界面时可以极大地节省时间。 如下图所示,这就是一个完整的应用的...http://www.raywenderlich.com/50310/storyboards-tutorial-in-ios-7-part-2

    Storyboards-Part1

    在"Storyboards - Part1"这个主题中,我们将深入探讨故事板的基本概念、使用方法以及它如何帮助开发者构建交互式的iOS应用。我们将重点关注以下几个方面: 1. **故事板概述**: 故事板(Storyboard)是Xcode IDE...

    iOS开发:我的初级到中级的晋级之路.zip

    在iOS开发领域,从初级阶段跃升至中级是一个充满挑战且有益的过程。在这个过程中,开发者不仅需要掌握基础的Swift编程语言,还要理解苹果的开发环境Xcode,以及iOS应用程序架构和设计模式。以下是对"iOS开发:我的...

    iOS Programming: The Big Nerd Ranch Guide (4th Edition) (Big Nerd Ranch Guides)

    2. iOS 7风格界面构建:本书详细介绍了如何利用iOS 7的美学特点来构建应用程序界面,使其更加美观且符合现代设计趋势。 3. 自动引用计数(ARC)与强弱引用: ARC是Objective-C语言中用于自动管理内存的一种机制,...

    iOS 10 App Development Essentials

    30. Implementing iOS 10 TableView Navigation using Storyboards in Xcode 8 31. Working with the iOS 10 Stack View Class 32. An iOS 10 Stack View Tutorial 33. An iOS 10 Split View Master-Detail Example ...

    Beginning iOS 7 Development: Exploring the iOS SDK 2014

    本书更新到了苹果公司最新最伟大的iOS 7 SDK以及Xcode的最新版本,包含了对iOS 7的全新技术和特性,例如故事板(Storyboards)和iCloud的相关介绍,并对现有材料进行了重要的更新。本书中的每个示例应用程序都是从头...

    IOS源码——ios适用于iOS 5、iOS 6和iOS 7的自定义NavigationBar.zip

    - iOS 7: 引入了扁平化设计,导航栏的背景变为透明,提供了新的UIAppearance API以支持全局样式修改。 2. **UIAppearance API**: - UIAppearance API允许开发者在应用程序范围内统一设置UI组件的外观,如颜色、...

    IOS6 in Practice

    《IOS6 in Practice》实战指南是一本专注于iOS6开发的深度实践书籍,旨在帮助开发者深入理解并掌握在iOS6平台上构建应用程序的技能。通过阅读本书,开发者不仅可以学习到iOS6的新特性和变化,还能获得实际操作的经验...

    iOS.9.App.Development.Essentials

    The key new features of iOS 9 and Xcode 7 are also covered in detail, including new error handling in Swift 2, designing Stack View based user interfaces, multiple storyboard support, iPad ...

    iOS.8.App.Development.Essentials

    Implementing iOS 8 TableView Navigation using Storyboards in Xcode 6 Chapter 30. An iOS 8 Split View Master-Detail Example Chapter 31. Implementing a Page based iOS 8 Application using ...

    《iOS 7 Programming Pushing the Limits》2014.04新书 英文版

    解压后获得文件:《iOS 7 Programming Pushing the Limits》.pdf 全高清文字版非图片扫描版,完整!! 其中的一些目录: Part I What’s New? C h a p t e r 1 The Brand New Stuff The New UI …… Enhancements to...

    Beginning iOS 6 Development: Exploring the iOS SDK [PDF]

    2. 苹果公司设备的兼容性:书籍描述中提到了iPhone 5和iPad 4,这表明在学习过程中,将会涉及到如何让应用在这些设备上运行,考虑到屏幕尺寸、分辨率和硬件特性。 3. Xcode的使用:Xcode是苹果公司开发的官方集成...

    ios7_in_action

    iOS7 in Action是一本关于iOS开发的入门书籍,适合那些对开发iOS应用程序感兴趣的人。本书详细介绍了iOS 7的基础知识和开发细节,涵盖了从iOS应用开发的基础入门到高级应用开发的知识体系。 首先,本书的第一部分是...

    Beginning iOS 6 Development: Exploring the iOS SDK

    iOS开发是移动应用开发领域的重要分支,随着智能手机和平板电脑的普及,掌握iOS开发技能变得越来越有价值。本书《Beginning iOS 6 Development: Exploring the iOS SDK》是一本关于iOS开发的入门书籍,尤其针对iOS 6...

    The iOS Apprentice 5th part 1 with Source Code

    5. **Storyboard**:Storyboards是iOS应用的主要设计工具,它们允许你通过一个可视化界面来构建应用的导航流程。理解如何在Storyboard中工作,包括Scene、Segue和ViewController的关联至关重要。 6. **...

Global site tag (gtag.js) - Google Analytics