iOS Storyboard全解析 《转帖》

iOS Storyboard全解析

 

来源:http://iaiai.iteye.com/blog/1493956html

Storyboard)是一个可以节省你不少设计手机App界面时间的新特性,下面,为了简明的说明Storyboard的效果,我贴上本教程所完成的Storyboard的截图: 
 
现 在,你就能够清楚的看到这个应用到底是干些什么的,也能够清楚的看到其中的各类关系,这就是Storyboard的强大之处了。若是你要制做一个页面不少 很复杂的App,Storyboard能够帮助你解决写不少重复的跳转方法的麻烦,节省不少时间,以便你可以彻底的专一于核心功能的实现上。 



开始 



首先启动Xcode,新建一个工程,咱们在这里使用Single View App Template,这个模板会提供一个类和一个Storyboard,免去咱们本身建立的麻烦。 
 
建立完成以后,Xcode的界面大概是这样的: 
 
这 个新的工程由两个类:AppDelegate和ViewController以及一个Storyboard组成(若是你选择了两个设备会有两个 Storyboard),注意这个项目没有xib文件,让咱们首先看看Storyboard是什么样的,双击Storyboard打开他: 
 
Storyboard 的样子和工做方式都和Interface Builder(如下简称为IB)像极了,你能够从左下方的控件库中拖动控件到你的View之中而且组织他们的排放顺序,惟一不一样的地方就 是,Storyboard不止是包含一个视图控件,而是全部的视图控件以及他们之间的关系。 


Storyboard对一个视图的官方术语是一个场景,可是一个场景其实就是一个ViewController,在iPhone中一次只可以展现一个场景,而在iPad中一次能够展现多个场景,好比Mail应用程序。 

经过尝试添加一些控件,你能够感觉一下Storyboard的工做方式。 
 
这个是数据显示器,显示全部场景及其控件的结构。 
 
在IB中,这个位置显示的是你的NIB文件中的文件,而在Storyboard中这里显示的是ViewController,目前这里只有一个ViewController,咱们接下来可能会增长一些。 

这是一个文档管理器的缩小版,叫作dock。 
 
Dock 展现场景中第一级的控件,每一个场景至少有一个ViewController和一个FirstReponder,可是也能够有其余的控件,Dock还用来简 单的链接控件,若是你须要向ViewController传递一个关系时,只须要将其按住Ctrl键拖到ViewController上就能够了。 

Note:你大概不会太长使用FirstResponder,由于它只是一个代理控件,表明着当前你所使用的控件。 

如今运行这个应用,他会向咱们设计的界面同样。 
 
若是你之前制做过NIB型的应用的话,你也许回去寻找MainWindow.xib ,这个文件包括全部的ViewController,Appdelegate等等,可是在Storyboard中这个特性已经被废止了。 
 
那么,没有这个文件,应用从那里起始呢? 

让咱们打开AppDelegate文件,看看那上面是怎么说的: 编程

  1. #import <UIKit/UIKit.h>  
  2.   
  3. @interface AppDelegate : UIResponder <UIApplicationDelegate>  
  4.   
  5. @property (strong, nonatomic) UIWindow *window;  
  6.   
  7. @end  


若是要使用Storyboard特性,那么AppDelegate必须继承自UIResponder类, 以前则是继承自NSObject类的,并且必须有一个不是UIOutlet类的Window属性声明才能够。 

如 果你再去看AppDelegate的执行文件,里面大概什么都没有,甚至连 application:didFinishLaunchingWithOptions: 也只是返回了一个 YES,而以前,这里则需声明一个ViewController而且将他设置成起始页面,可是如今这些都没有了。 

秘密就在info.plist文件中, 打开Ratings-Info.plist (在 Supporting Files group里) 你就会看到这些: 
 
在 NIB为UI的应用里,info.plist文件中有一个键兼作NSMainNibFile,或者叫作Main nib file base name,他用来指示UIApplication载入MainWindow.xib,而且将他与应用连接起来,而如今这个键值消失了。 

而 Storyboard应用则利用 UIMainStoryboardFile,或者 “Main storyboard file base name” 键值来表示当App初始化时的Storyboard名称,当程序运行时,UIApplication会使用 MainStoryboard.sotryboard做为第一加载项,而且将他的UIWindow展现在屏幕上,不须要任何编程工做。 

在项目总结面板上,你也能够看到而且编辑这些信息: 
 
若是你还想设置nib文件的话,另外有地方去设置的。 

为了完成这个实验性的小程序,咱们打开main.m,加入 小程序

  1. #import <UIKit/UIKit.h>  
  2.   
  3. #import "AppDelegate.h"  
  4.   
  5. int main(int argc, char *argv[])  
  6. {  
  7.     @autoreleasepool {  
  8.         return UIApplicationMain(argc, argv, nil,  
  9.             NSStringFromClass([AppDelegate class]));  
  10.     }  
  11. }  


以前是UIApplicationMain()的函数如今是空的, 变成了 NSStringFromClass([AppDelegate class]). 

与 以前使用MainWindow.xib的一个最大的不一样是:如今app delegate已经不是Storyboard的一部分了,这是由于app delegate再也不从nib文件中,而侍从Storyboard中加载了,咱们必须告诉 UIApplicationMain 咱们的app delegate类的名字是什么,不然他将没法找到。 





制做一个Tab类型的应用 

本教程中的Rating App拥有两个Tab,在Storyboard中,很轻松就可以作出一个Tab视图。 

回到MainStoryboard.storyboard中,直接从左边的Library拖进来一个TabViewController就能够了。 
 
新 的Tab Bar Controller附带了两个View controller,分别做为Tab的视图使用,UITabBarController被称为包含视图,由于他包含这其余一些View,其余常见的包含 视图还有那vi嘎提鸥鸟 Controller和SplitView Controller。 

在iOS 5中,你还能够本身写一个自定义的Controller,这在之前是作不到的。 

包含关系在Storyboard中用一下这种箭头表示。 
 
拉一个Label控件到第一个子试图中,命名为“First Tab”,再在第二个子视图中添加一个Label,命名为“Second Tab”。 

注意:当屏幕的缩放大于100%时,你没法在单个场景中添加控件。 

选中Tab Bar Controller,进入属性检查器,选中“做为起始场景”,以下图: 
 
如今那个没有头的虚虚的小箭头指向了Tab Bar Controller,说明他是起始场景。 
 
这意味着,当你启动这个应用的时候,UIApplication将会将这个场景做为应用的主屏幕。 

Storyboard必定要有一个场景是起始场景才行。 

如今运行试试吧 
 
code专门为创造这种Tab Bar的应用准备了一个模板,咱们也可使用他,可是本身有能力不用模板本身作一个Tab Bar也是不错的事。 

若是你添加了多于五个子视图到一个TabBarcontroller的话,并不会创造五个Tab,第四个tab会自动变成More标签,不错吧 




制做一个表格视图 

目前链接到Tab bar Controller的视图都是普通的View Controller,如今,我要用一个TableViewController来代替其中的一个ViewController。 

单击第一个视图并删除,从Library中拖出一个TableViewController。 
 
在选中这个TableViewController的前提下,从Library中拖出一个NavController,将会直接附着在上面。 
 
固然也能够调换顺序,我彻底没意见。 

因为NavController和TabBarController同样也是一个包含控制器视图,因此他也必须包含另外一个视图,你能够看到一样的箭头链接者这两个View。 
 
请注意全部嵌套在NavController下的View都会有一个Navigation Bar,你没法移除它,由于他是一个虚拟的Bar。 

若是你检视属性检测器,你就会发现全部bar的属性都在一块儿: 
 
“Inferred”是Storyboard中的默认设置,他意味着继承的关系,可是你也能够改变他。可是请注意这些设置都是为了让你更好的进行设计和这样设置的,随意修改默认设置会带来不可碰见的后果,施主自重。 

如今让咱们把这个新的场景链接到Tab Bar Controller中,按住Ctrl拖动,或者右键。 
 
当你放手的时候,一个提示框会出现。 
 
固然是选第一个了,Relationship – viewControllers ,这将自动建立两个场景之间的关系。 
 
 
直接拖动就能够改变Tab Item的顺序,同时也会改变显示Tab的顺序,放在最左边的Tab会第一个显示。 

如今运行试试看吧 
 
在咱们在这个应用中加入任何实质性的功能以前,咱们先来清理一下Storyboard,你不须要改变TabBarController中的任何内容而只须要改变他的子视图就能够了。 

每当你链接一个新的视图到TabBarController中的时候,他就会自动增长一个Tab Item,你可使用他的子视图来修改该Item的图片和名称。 

在NavController中选中Tab Item而且在属性编辑其中将其修改成Player。 
 
将第二个Tab Item命名为“Gesture” 

咱们接下来把自定义的图片加入到这些item中, 源码 中包含一个名为“Image”的文件夹,在那里你能够找到咱们用到的资源。 

接下来,将NavController的title改成Player,也可使用代码·· 
 
运行看一看,难以置信吧,你到如今也没写一条代码。 
 





原型表格单元 

你也许已经注意到了,自从咱们加入了Table View Controller以后,Xcode便会现实下面这样一条警告。 
 
这条警告是:“Unsupported Configuration: Prototype table cells must have reuse identifiers”意思是,原型表格单元必须有一个身份证(意译啦) 

原 型单元格是另外一个Storyboard的好特性之一。在以前,若是你想要自定义一个Table Cell,那么你就不得不用代码来实现,要么就要单首创建一个Nib文件来表示单元格内容,如今你也能够这样作,不过原型单元格能够帮你把这一过程大大的 简化,你如今能够直接在Storyboard设计器中完成这一过程。 

Table View如今默认的会带有一个空白的原型单元格,选中他,在属性控制器中将他的Style改成subtitle,这样的话,每一格就会有两行字。 
 
 
将附件设置为Disclosure Indicator而且将这个原型单元格的Reuse Identifier 设置喂“PlayerCell”,这将会解决Xcode所警告的问题。 

试着运行一个,发现什么都没变,这并不奇怪,由于咱们尚未给这个表格设置一个数据来源(DataSource),用以显示。 

新建一个文件,使用UIViewContoller模板,命名为 PlayersViewController ,设置喂UITableViewController的子类,不要勾选创建XIB文件。 

回到Storyboard编辑器,选择Table View Controller,在身份控制器中,把他的类设置为PlayerViewController,这对于把Storyboard中的场景和你自定义的子类挂钩是十分重要的。要是不这么作,你的子类根本没用。 
 
如今起,当你运行这个应用时,table view controller实际上是PlayersViewContoller的一个实例。 

在 PlayersViewController.h 中声明一个MutableArray(可变数组) 数组

  1. #import <UIKit/UIKit.h>  
  2.   
  3. @interface PlayersViewController : UITableViewController  
  4.   
  5. @property (nonatomic, strong) NSMutableArray *players;  
  6.   
  7. @end  


这个数组将会包含咱们的应用的主要数据模型。咱们如今加一些东西到这个数组之中,新建一个使用Obj-c模板的文件,命名为player,设置喂NSObject的子类,这将会做为数组的数据容器。 

编写Player.h以下: app

  1. @interface Player : NSObject  
  2.   
  3. @property (nonatomic, copy) NSString *name;  
  4. @property (nonatomic, copy) NSString *game;  
  5. @property (nonatomic, assign) int rating;  
  6.   
  7. @end  


编写Player.m以下: 框架

  1. #import "Player.h"  
  2.   
  3. @implementation Player  
  4.   
  5. @synthesize name;  
  6. @synthesize game;  
  7. @synthesize rating;  
  8.   
  9. @end  


这里没有什么复杂的,Player类只是一个容器罢了,包含三个内容:选手的名字、项目和他的评级。 

接下来咱们在App Delegate中声明数组和一些Player对象,并把他们分配给PlayerViewController的players属性。 

在AppDelegate.m中,分别引入(import)Player和PlayerViewController这两个类,以后新增一个名叫players的可变数组。 编辑器

  1. #import "AppDelegate.h"  
  2. #import "Player.h"  
  3. #import "PlayersViewController.h"  
  4.   
  5. @implementation AppDelegate {  
  6.     NSMutableArray *players;  
  7. }  
  8.   
  9. // Rest of file...  


修改didFinishLaunchingWithOptions方法以下: ide

  1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions  
  2. {  
  3.     players = [NSMutableArray arrayWithCapacity:20];  
  4.     Player *player = [[Player alloc] init];  
  5.     player.name = @"Bill Evans";  
  6.     player.game = @"Tic-Tac-Toe";  
  7.     player.rating = 4;  
  8.     [players addObject:player];  
  9.     player = [[Player alloc] init];  
  10.     player.name = @"Oscar Peterson";  
  11.     player.game = @"Spin the Bottle";  
  12.     player.rating = 5;  
  13.     [players addObject:player];  
  14.     player = [[Player alloc] init];  
  15.     player.name = @"Dave Brubeck";  
  16.     player.game = @"Texas Hold’em Poker";  
  17.     player.rating = 2;  
  18.     [players addObject:player];  
  19.     UITabBarController *tabBarController =  
  20.      (UITabBarController *)self.window.rootViewController;  
  21.     UINavigationController *navigationController =  
  22.      [[tabBarController viewControllers] objectAtIndex:0];  
  23.     PlayersViewController *playersViewController =  
  24.      [[navigationController viewControllers] objectAtIndex:0];  
  25.     playersViewController.players = players;  
  26.     return YES;  
  27. }  


这将会创造一些Player对象并把他们加到数组中去。以后在加入: 函数

  1. UITabBarController *tabBarController = (UITabBarController *)  
  2.   self.window.rootViewController;  
  3. UINavigationController *navigationController =  
  4.   [[tabBarController viewControllers] objectAtIndex:0];  
  5. PlayersViewController *playersViewController =  
  6.   [[navigationController viewControllers] objectAtIndex:0];  
  7. playersViewController.players = players;  


咦,这是什么?目前的状况是:咱们但愿可以将players数组链接到PlayersViewController的players属性之中以便 让这个VC可以用作数据来源。可是app delegate根本不了解PlayerViewController到底是什么,他将须要在storyboard中寻找它。 

这是一个 我不是很喜欢storyboard特性,在IB中,你在MainWindow.xib中老是会有一个指向App delegate的选项,在那里你能够在顶级的ViewController中向Appdelegate设置输出口,可是在Storyboard中目前这 还不可能,目前只能经过代码来作这样的事情。 工具

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


咱们知道storyboard的起始场景是Tab Bar Controller,因此咱们能够直接到这个场景的第一个子场景来设置数据源。 

PlayersViewController 在一个NavController的框架之中,因此咱们先看一看UINavigationController类: 

  1. UINavigationController *navigationController = [[tabBarController  
  2.   viewControllers] objectAtIndex:0];  


而后询问它的根试图控制器,哪个是咱们要找的PlayersViewController: 

  1. PlayersViewController *playersViewController =  
  2.   [[navigationController viewControllers] objectAtIndex:0];  


可是,UIViewController根本就没有一个rootViewController属性,因此咱们不能把数组加入进去,他又一个topViewController可是指向最上层的视图,与咱们这里的意图没有关系。 

如今咱们有了一个装在了players物体合集的数组,咱们继续为PlayersViewController设置数据源。 

打开PlayersViewController.m,加入如下数据源方法: 

  1. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView  
  2. {  
  3.     return 1;  
  4. }  
  5.   
  6. - (NSInteger)tableView:(UITableView *)tableView  
  7.   numberOfRowsInSection:(NSInteger)section  
  8. {  
  9.     return [self.players count];  
  10. }  


真正起做用的代码在cellForRowAtIndexPath方法里,默认的模板是以下这样的: 

  1. - (UITableViewCell *)tableView:(UITableView *)tableView  
  2.   cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  3. {  
  4.     static NSString *CellIdentifier = @"Cell";  
  5.   
  6.     UITableViewCell *cell = [tableView  
  7.       dequeueReusableCellWithIdentifier:CellIdentifier];  
  8.     if (cell == nil) {  
  9.         cell = [[UITableViewCell alloc]  
  10.           initWithStyle:UITableViewCellStyleDefault  
  11.           reuseIdentifier:CellIdentifier];  
  12.     }  
  13.   
  14.     // Configure the cell...  
  15.     return cell;  
  16. }  


无疑这就是之前设置一个表格视图的方法,不过如今已经革新了,把这些代码修改以下: 

  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  2. {  
  3.     UITableViewCell *cell = [tableView  
  4.       dequeueReusableCellWithIdentifier:@"PlayerCell"];  
  5.     Player *player = [self.players objectAtIndex:indexPath.row];  
  6.     cell.textLabel.text = player.name;  
  7.     cell.detailTextLabel.text = player.game;  
  8.     return cell;  
  9. }  


这看上去简单多了,为了新建单元格,你只需使用以下代码: 

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


若是没有现存的单元格能够回收,程序会自动创造一个原型单元格的复制品以后返回给你,你只须要提供你以前在Storyboard编辑视图中设置的身份证就能够的,在这里就是“PlayerCell”,若是不设置这个,这个程序就没法工做。 

因为这个类对于Player容器目前一无所知,因此咱们须要在文件的开头加入一个引入来源 

  1. #import "Player.h"  


记得要建立synthesize语句哦亲 

  1. @synthesize players;  


如今运行应用,会看到Table里有着players容器。 
 
请注意:咱们这里只使用一种单元格原型,若是你须要使用不一样类型的单元格的话,只须要在storyboard中另外加入一个单元格原型就能够了,不过不要忘记给他们指派不一样的身份证。 




设计自定义的原型单元格 

对于不少应用来讲,使用默认的单元格风格就OK了,可是我恰恰要在每个单元格的右边加上一个一个图片来表示选手的评级,可是添加图片对于默认类型的单元格来讲并不支持,咱们须要自定义一个设计。 

让咱们转回MainStoryboard.storyboard,选中table view中的prototype cell,把它的Style attribute改成Custom,全部默认的标签都会消失。 

首先把单元格变得更高一些,你能够直接拉它,也能够在大小控制器中修改数字,我在这里使用55点的高度。 

从 Objects Library中拖出两个标签物体,按照以前的样式安插到单元格里,记得设置label的Highlighted颜色为白色,那样的话当单元格被选中的时候会看起来更好看一些。 

以后添加一个Image View对象,将它放置在单元格的右边,设置他的宽度为81点,高度并不重要,在属性检查器中设置模式为置中。 

我把标签设置为210点长以确保他不会和ImageView重合,最后总体的设计会看起来象下面这样: 
 
因为这是一个自定义的单元格,因此咱们不可以使用UITableView默认的textLabel和detailLabel来设置数据,这些属性也再也不指向咱们的单元格了,咱们使用标签(tags)来指定标签。 

将Name标签的tag设置为100,Game的设置喂101,image的设置喂102,在属性检查器里设置哦亲。 

以后打开 PlayersViewController.m ,在PlayersViewcontroller中将cellForRowatIndexPath修改成: 

  1. - (UITableViewCell *)tableView:(UITableView *)tableView  
  2.   cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  3. {  
  4.     UITableViewCell *cell = [tableView  
  5.       dequeueReusableCellWithIdentifier:@"PlayerCell"];  
  6.     Player *player = [self.players objectAtIndex:indexPath.row];  
  7.     UILabel *nameLabel = (UILabel *)[cell viewWithTag:100];  
  8.     nameLabel.text = player.name;  
  9.     UILabel *gameLabel = (UILabel *)[cell viewWithTag:101];  
  10.     gameLabel.text = player.name;  
  11.     UIImageView * ratingImageView = (UIImageView *)  
  12.       [cell viewWithTag:102];  
  13.     ratingImageView.image = [self imageForRating:player.rating];  
  14.     return cell;  
  15. }  


这里是用了一个新的方法,叫作ImageRating,在 cellForRowAtIndexPath方法以前加入这个方法: 

  1. - (UIImage *)imageForRating:(int)rating  
  2. {  
  3.     switch (rating)  
  4.     {  
  5.         case 1: return [UIImage imageNamed:@"1StarSmall.png"];  
  6.         case 2: return [UIImage imageNamed:@"2StarsSmall.png"];  
  7.         case 3: return [UIImage imageNamed:@"3StarsSmall.png"];  
  8.         case 4: return [UIImage imageNamed:@"4StarsSmall.png"];  
  9.         case 5: return [UIImage imageNamed:@"5StarsSmall.png"];  
  10.     }  
  11.     return nil;  
  12. }  


这就完成了,运行看看: 
 
这 和咱们想象的结果并非很符合,咱们修改了原型单元格的属性和高度,可是table view却没有考虑进去,有两种方法能够修复它,咱们能够改变table view的行高或者加入 heightForRowAtIndexPath 方法来修改,地一种方法更简单,咱们就用他。 

注意:在一下两种状况下,你应该使用 heightForRowAtIndexPath 方法:一是,你不能预先知道你的单元格的高度,二是不一样的单元格会有不一样的高度。 

回到MainStoryboard.storyboard,在大小检查器中将高度设置为55: 
 
经过这种方式的话,若是以前你是使用拖动而不是键入数值的方式改变高度的属性的话,则table view的数值也会自动改变。 

如今运行看看,好多了吧 





为原型单元格设置子类 

咱们的表格视图已经至关像模像样了,可是我并非很喜欢使用tag来访问label,要是咱们可以把这些lable链接到输出口,以后在回应属性中使用他们,该多好,并且不出所料,咱们能够这样作。 

使用 Objective-C class模板新建一个文件,命名为PlayerCell,继承UITableViewCell。 

修改PlayerCell.h 

  1. @interface PlayerCell : UITableViewCell  
  2.   
  3. @property (nonatomic, strong) IBOutlet UILabel *nameLabel;  
  4. @property (nonatomic, strong) IBOutlet UILabel *gameLabel;  
  5. @property (nonatomic, strong) IBOutlet UIImageView  
  6.   *ratingImageView;  
  7.   
  8. @end  


修改PlayerCell.m 

  1. #import "PlayerCell.h"  
  2.   
  3. @implementation PlayerCell  
  4.   
  5. @synthesize nameLabel;  
  6. @synthesize gameLabel;  
  7. @synthesize ratingImageView;  
  8.   
  9. @end  


这个类自己并不其很大的做用,只是为nameLabel、gameLabel和ratingImageView声明了属性。 

回 到MainStoryboard.storyboard选中原型单元格,将他的class属性修改成“PlayerCell”,如今当你向table view请求dequeueReusableCellWithIdentifier,他会返回一个PlayerCell实例而不是一个普通的 UITableViewCell实例。 

请注意我将这个类和reuse Indetifier的名字命名的同样,只是营卫我喜欢这样哦亲,这两个之间其实没啥关系。 

如今你能够将标签和image view链接到输出口去了,选中或者将他从连接检查器拖动到table view cell。 
 
请注意:要把这个control链接到table view cell而不是view controller哦亲,别选错了。 

如今咱们把一切都连接好了,只须要加入数据源的代码就能够了。 

  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  2. {  
  3.     PlayerCell *cell = (PlayerCell *)[tableView  
  4.      dequeueReusableCellWithIdentifier:@"PlayerCell"];  
  5.     Player *player = [self.players objectAtIndex:indexPath.row];  
  6.     cell.nameLabel.text = player.name;  
  7.     cell.gameLabel.text = player.game;  
  8.     cell.ratingImageView.image = [self  
  9.       imageForRating:player.rating];  
  10.     return cell;  
  11. }  


咱们如今将接收到 dequeueReusableCellWithIdentifier 的控件指派到PlayerCell,只须要简单的使用已经连接labels和image view到设置好的属性上就能够了,这会让这个设计看上去更加好控制,更加简明。 

固然,在PlayerCell前要引入资源: 

  1. #import "PlayerCell.h"  


试着运行,你会发现其实什么都没有变化,但是咱们都知道,内部已经有了变化。 

在这相同的场景下面,咱们但是在使用子类呢。 

这里还有一些设计小窍门:第一点:必定要设置标签被选中时的颜色。 
 
第二点,确保你加入单元格的字符大小是能够变化的,这样,当单元格大小变化时,他的内容的大小也会跟着变化,好比说: 

在PlayersViewController.m中加入以下方法: 

  1. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath  
  2. {  
  3.     if (editingStyle == UITableViewCellEditingStyleDelete)  
  4.     {  
  5.         [self.players removeObjectAtIndex:indexPath.row];  
  6.         [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];  
  7.     }  
  8. }  


这个方法加入好了以后,用手指轻扫一行单元格,会出现一个删除键,试试看 
 
Delete按钮出如今右边,遮住了一部分评级图片,怎么解决呢? 

打开MainStoryBoard.storyboard,选中table view cell中的image view,在大小检查器中修改Autosizing属性,是它可以跟随上级view的边缘。 
 
为labels设置一样的属性。 
 
加入了这些变更以后,删除按钮如咱们意料的出现了: 
 
其实,最好的作法是让这些星星在出现delete按钮的时候消失,不过这只是一个练习,不要太较真哦亲

 
若是你想了解更多Storyboard的特性,那么你就来对了地方,下面咱们就来接着上次的内容详细讲解Storyboard的使用方法。 

在上一篇《Storyboard全解析-第一部分》中,咱们介绍了如何使用storyboard来制做多种场景和如何将这些场景连接起来,咱们还学习了如何自定义一个表格视图。 

接下来这部分,也是最后一部分,咱们将讲解联线(segue),静态单元格等内容,咱们还将加入一个选手详细内容页面,和一个游戏选择页面。 



Segues的介绍 

如今,让咱们建立一个场景使用户能够本身增长新的选手进入列表。 

在Players界面中拖入一个Bar Button,放置在导航栏的右侧,在属性监视器中将他的Identifier改成“add”,这样他就会显示一个加号的按钮,当用户点击这个按钮时,他就会弹出一个新的场景让用户对新的内容进行编辑或添加。

在编辑器中拖入一个新的Table View Controller,放置在Players场景的右边,而后按住ctrl,拉动加号键到新的场景中,这样,这个场景就会自动和这个按钮创建联系,从而自动纳入Navigation View Controller中。 
 
放开鼠标以后,会出现以下选项: 
 
选中Modal,你能够注意到出现了一种新的箭头形式: 
 
这 种连接形式被官方称为segue(pronounce: seg-way),我叫它联线,(实际上是转换的意思)这种形式的联线是表示从一种场景转换到另一种场景中,以前咱们使用的链接都是描述一种场景包含另外一 种场景的。而对于联线来讲,它会改变屏幕中显示的内容,并且必须由交互动做触发:如轻点,或其余手势。 

联线真正了不得的地方在于:你再也不须要写任何代码来转入一个新的场景,也不用在将你的按钮和IBAction链接到一块儿,咱们刚才作的,直接将按钮和场景连接起来,就可以完成这项工做。 

运行这个app,按下 + 键,会发现出现了一个新的列表。 
 
这种叫作 “modal” segue(模态转换),新的场景彻底盖住了旧的那个。用户没法再与上一个场景交互,除非他们先关闭这个场景,过一会咱们会讨论 push segue,这种segue会把场景推入导航栈。 

新的场景如今尚未什么用,你甚至不能把他关闭呢。 

联 线只可以把你送到新的场景,你要是想回来,就得使用delegate pattern,代理模式。咱们必须首先给这个新的场景设置一个独有的类,新建一个继承UITableViewController的类,命为 PlayerDetailsViewController。 

为了把它和storyboard相连,回到MainStoryBoard,选择新建的那个Table View Contrller,将他的类设置喂PlayerDetailViewController,千万不要忘记这一步,这很重要。 

作完这一步以后,把新场景的标题改成“Add Player”,分别加入“Done”和“Cancel”两个导航栏按钮。 
 
修改PlayerDetailsViewController.h 以下: 
  1. @class PlayerDetailsViewController;  
  2.   
  3. @protocol PlayerDetailsViewControllerDelegate <NSObject>  
  4. - (void)playerDetailsViewControllerDidCancel:  
  5.   (PlayerDetailsViewController *)controller;  
  6. - (void)playerDetailsViewControllerDidSave:  
  7.   (PlayerDetailsViewController *)controller;  
  8. @end  
  9.   
  10. @interface PlayerDetailsViewController : UITableViewController  
  11.   
  12. @property (nonatomic, weak) id <PlayerDetailsViewControllerDelegate> delegate;  
  13.   
  14. - (IBAction)cancel:(id)sender;  
  15. - (IBAction)done:(id)sender;  
  16.   
  17. @end  

这会声明一个新的代理机制,当用户点击Cancel或者done按钮时,咱们将用它来交互Add Player场景和主场景通信。 

回到故事版编辑器,将Cancel和Done按钮分别与动做方法链接,一种方式是,按住Ctrl拖动到ViewController上,以后选择正确的动做。 
 
在 PlayerDetailsViewController.m,加入以下代码: 
  1. - (IBAction)cancel:(id)sender  
  2. {  
  3.     [self.delegate playerDetailsViewControllerDidCancel:self];  
  4. }  
  5. - (IBAction)done:(id)sender  
  6. {  
  7.     [self.delegate playerDetailsViewControllerDidSave:self];  
  8. }  

这是两个导航栏按钮要使用的方法,如今只须要让代理知道咱们刚才加入了代码,而真正关闭场景只是代理的事情。 

通常来讲必定要为代理制定一个对象参数,这样他才知道向那里发送信息。 

不要忘记加入Synthesize语句。 
  1. @synthesize delegate;  

如今咱们已经为PlayerDetailsViewController设置了一个代理协议,咱们须要将这个协议的实现方法 (implement)写在什么地方,很明显应该写在PlayerViewController由于这个vc表明了Add Player场景。在PlayersViewController.h中加入以下代码: 
  1. #import "PlayerDetailsViewController.h"  
  2.   
  3. @interface PlayersViewController : UITableViewController <PlayerDetailsViewControllerDelegate>  

并在PlayersViewController.m的结尾加入: 
  1. #pragma mark - PlayerDetailsViewControllerDelegate  
  2.   
  3. - (void)playerDetailsViewControllerDidCancel:  
  4.   (PlayerDetailsViewController *)controller  
  5. {  
  6.     [self dismissViewControllerAnimated:YES completion:nil];  
  7. }  
  8.   
  9. - (void)playerDetailsViewControllerDidSave:  
  10.   (PlayerDetailsViewController *)controller  
  11. {  
  12.     [self dismissViewControllerAnimated:YES completion:nil];  
  13. }  

目前这个代理方法只可以跳转到这个新的场景中,接下来咱们来让他作一些更为强大的事情。 

iOS 5 SDK中新添加的dismissViewControllerAnimated:completion: 方法能够被用来关闭一个场景。 

最后还有一件事情须要作,就是Players场景须要告诉PlayerDetailsVC他的代理在哪里,听上去这种工做在故事版编辑其中一拖就好了,实际上,你得使用代码才能完成。 

将如下方法加入到 PlayersViewController 中 
  1. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender  
  2. {  
  3.     if ([segue.identifier isEqualToString:@"AddPlayer"])  
  4.     {  
  5.         UINavigationController *navigationController =  
  6.           segue.destinationViewController;  
  7.         PlayerDetailsViewController  
  8.           *playerDetailsViewController =  
  9.             [[navigationController viewControllers]  
  10.               objectAtIndex:0];  
  11.         playerDetailsViewController.delegate = self;  
  12.     }  
  13. }  

当使用Segue的时候,就必须加入这个名叫 prepareForSegue 的方法,这个新的ViewController在被加载的时候仍是不可见的,咱们能够利用这个机会来向他发送数据。 

请注意,这个segue的最终目标是Navigation Controller,由于这个是咱们连接在导航栏上的按钮,为了获取PlayerDetailsViewController实例,咱们必须经过NavController的属性来获取。 

试着运行一下这个应用,单击 + 键,而后试着关闭Add Player场景,仍然无论用。 

这是由于咱们没有给Segue指定一个identifier,而parepareForSegu须要检查AddPlayer的身份证,这是必须的,由于你有可能会同时使用多个联线。 

为了解决这个问题,进入Storyboard的编辑器,点击Players场景和NavgationViewController场景之间的联线,你会注意到与这个连线相关的按钮会自动亮起来。 

在属性监视器中,将Identifier设置喂“AddPlayer” 
 
若是这是你再次运行这个应用,点击“Cancel”或者“Done”按钮,这个场景就会自动关闭而且返回到上一级场景。 

注 意:从modal场景调用dismissViewControllerAnimated:completion方法是咱们在这里使用的,可是这并不意味着 你必须这样作。可是,若是你不是代理来完成这个关闭窗口的工做的话,惟一须要注意的是,若是你以前使用了 [self.parentViewController dismissModalViewControllerAnimated:YES] 语句来关闭窗口的话,那么这个语句就不会正常工做了。 

顺便说一下,属性检查器中有一个Transition的选项,在这里你能够选择场景转换是的动画效果。 
 
试着运行一下,看看那种动画你最喜欢吧,但事情不要改变Style这个选项,若是你改变了,这个app可能会crash哦。 

咱们接下来在这个教程中还会用到几回代理方法,下面咱们来列一下为了完成一个连线,你须要作的几件事情。 

  • 首先,从起始的控件作一条联线到目标场景。
  • 将这个联线制定一个独特的Identifier。
  • 为目标场景制做一个代理方法。
  • 在Cancel和Done按钮,以及全部其余你须要和原始场景交流的地方调用代理方法。
  • 在原始场景执行代理方法,这将会在用户按下按钮后关闭场景。
  • 在原始场景执行prepareForSegue方法。


咱们在这里必须使用代理,是由于根本没有反向联线这种东西,当sugue被启动以后,他将会创造出一个目标场景的新实例。你固然能够作一个从目标场景回到原始场景的联线,可是结果可能与你但愿的截然不同。 

距离来讲吧,若是你作一条从cancel按钮回到原始场景的连线的话,他并不会关闭当前场景并返回原始场景,而是会建立一个原始场景的新实例,这种状况会不停循环,知道把内存耗尽为止。 

因此请记住:segue只用于打开新的场景。 





静态单元格 

当咱们所有完成以后,Add Player场景会看上去象下面的同样: 
 
这是一种分组表格视图,可是不一样的是,咱们并不须要为这个表哥建立一个数据源,咱们能够在故事版编辑器中直接设计这个视图,而不须要重写cellForRowAtIndex方法,使得咱们能够这样作的秘诀就是静态单元格。 

选中Add Player场景,以后在属性检查器中,将Content属性改成StaticCell,将Style to Grouped属性修改成2。 
 
当你修改Section属性时,编辑器会复制一个现有的组。你也能够本身选中一个组后选择Duplicate。 

咱们的这个场景每一个组只须要用一个行,因此选中上面的那个行以后删除。 

选中顶行,修改Header的值为:“Player Name”. 
 
拖一个新的Text Field进入这个组的单元格里,把它的边界删除掉,使用System 17字体,取消Adjust to Fit选项。 

我 们如今在PlayerDetailsViewController中使用Assistant Editor这个Xcode 4.x的新特性来建立一个输出 口给这个Text Field,在工具栏的按钮中打开Assistant Editor,那玩意看起来像个外星人,我指的是按钮。 

选中text field,按住Ctrl,将他拖到打开的文件之中。 
 
放开鼠标,会出现一个选单。 
 
将这个新的书出口命名为nameTextField,在你肯定连接以后,Xcode会自动建立下列代码: 
  1. @property (strong, nonatomic) IBOutlet UITextField *nameTextField;  

他还会自动建立Synthesize语句,并同时在viewDidLoad文件中建立方法。 

永远别在动态表格中使用这种拖来拖去的方法,可是对于静态单元格来讲就OK,对于每一个静态单元格来讲都必须建立一个新的实例。 

将第二个组的静态单元格的Style设置为Right Detail,这将会建立一个标准的单元格,把左侧的label的内容修改成Game,设置一个Disclosure Indicator,为右侧Detail的label设置一个输出口。 

最终的设计完成后是这样的: 
 
当 你使用静态单元格的时候,你的Table View Controller就不须要一个数据源了,可是由于咱们使用了Xcode的模板来创造 PlayerDetailsViewController这个类,他里面仍然有一些默认的数据源设置代码,让咱们来删除之。在如下这个标志 
  1. #pragma mark - Table view data source  

和这个标志之间的代码所有删除。 
  1. #pragma mark - Table view delegate  

如今运行这个App,效果不错吧,请注意咱们不但一行代码也没写,还删除了好些。 

可是咱们并不可以彻底避免写任何代码,你可能已经注意到了,在文本框和单元格周围有一些空间,用户在完成编辑以后单击这些区域并不会结束键盘什么的,怎么避免这个问题呢?用下面的代码代替tableView:didSelectRowatIndex方法。 
  1. - (void)tableView:(UITableView *)tableView  
  2.   didSelectRowAtIndexPath:(NSIndexPath *)indexPath  
  3. {  
  4.     if (indexPath.section == 0)  
  5.         [self.nameTextField becomeFirstResponder];  
  6. }  

这些代码就是说:若是用户点击第一个单元格后咱们激活text field控件,这虽然是细节,可是细节决定成败。 

同时你也须要在属性检查器的Selection Style选项改成None。 

OK,咱们的设计所有完成了。 




增长一个选手吧 

如今咱们暂时先忽略Game这一行,先让用户可以编辑选手的状况以后再说。 

当用户单击Cancel键的时候,无论做出什么修改都会被弃置,场景也会关闭并返回上一级菜单。这一块的程序咱们已经作好了,也就是咱们刚才作得一个代理方法,它接收到did cancel这个方法以后就会关闭这个视图。 

可是当用户单击“Done”这个按钮时,咱们应该建立一个新的选手项目而后加入他的属性,以后咱们还须要通知代理器咱们新增了一个选手,以便它可以更新上一级菜单。 

在 PlayerDetailsViewController.m,把完成的方法改为: 
  1. - (IBAction)done:(id)sender  
  2. {  
  3.     Player *player = [[Player alloc] init];  
  4.     player.name = self.nameTextField.text;  
  5.     player.game = @"Chess";  
  6.     player.rating = 1;  
  7.     [self.delegate playerDetailsViewController:self  
  8.      didAddPlayer:player];  
  9. }  

这须要咱们引进Player的头文件: 
  1. #import "Player.h"  

这个完成方法会建立一个新的Player实例,并把它发送给代理器,因为目前代理器尚未这个方法,因此咱们须要在PlayerDetailsViewController的头文件中修改以下代码: 
  1. @class Player;  
  2.   
  3. @protocol PlayerDetailsViewControllerDelegate <NSObject>  
  4. - (void)playerDetailsViewControllerDidCancel:  
  5.   (PlayerDetailsViewController *)controller;  
  6. - (void)playerDetailsViewController:  
  7.   (PlayerDetailsViewController *)controller  
  8.   didAddPlayer:(Player *)player;  
  9. @end  

这个“Did Save”的方法的声明没有了,咱们加入一个“didAddPlayer”方法。 

下面咱们须要在执行文件中加入执行的方法,打开PlayersViewController.m,加入: 
  1. - (void)playerDetailsViewController:  
  2.   (PlayerDetailsViewController *)controller  
  3.   didAddPlayer:(Player *)player  
  4. {  
  5.     [self.players addObject:player];  
  6.     NSIndexPath *indexPath =  
  7.      [NSIndexPath indexPathForRow:[self.players count] - 1  
  8.        inSection:0];  
  9.     [self.tableView insertRowsAtIndexPaths:  
  10.       [NSArray arrayWithObject:indexPath]  
  11.        withRowAnimation:UITableViewRowAnimationAutomatic];  
  12.     [self dismissViewControllerAnimated:YES completion:nil];  
  13. }  

第一个语句向players的数组中加入新的Player对象,以后他会通知表格视图:一个新的行已经被建立,这是由于table  view和他的数据源必须一直是同步的才行,咱们其实也可使用[self.tableView reloadData]这个语句,可是从新建立一个单元格会有随之而来的动画,看起来更好看一些。 UITableViewRowAnimationAutomatic是一个iOS 5的新特性,使各行自动选择合适的动画效果出现,很是好用。 

如今试试看,你应该可使用按钮加入新行到表视图中了。 

如 果你已经开始担忧storyboard的性能了,那么不用担忧。就算是将全部的场景都一块载入的话,也不会消耗多少资源的。storyboard不会一下 子加载全部的ViewController,而是会加载起始场景,在这里是Tab View,再从起始场景加载其余与起始场景相关的场景。 

可是其余场景知道联线到他们以前是不会被加载的。而这些场景在你返回以后都会卸载,因此只有当前场景会在内存中,就像你以前在用分开的nib文件同样的。 

咱们经过实验来看一看。在PlayerDetailsViewController.m中加入下面的方法: 
  1. - (id)initWithCoder:(NSCoder *)aDecoder  
  2. {  
  3.     if ((self = [super initWithCoder:aDecoder]))  
  4.     {  
  5.         NSLog(@"init PlayerDetailsViewController");  
  6.     }  
  7.     return self;  
  8. }  
  9. - (void)dealloc  
  10. {  
  11.     NSLog(@"dealloc PlayerDetailsViewController");  
  12. }  

咱们重写了initWithCoder和dealloc方法,使得debug控制台输出一个很长的信息。这时候运行这个app,你会发现除非按下segue的按钮,不然新的场景不会被初始化,放心了吧。 

还有一件关于静态单元格的事情须要注意,那就是他们只可以在UITableViewController的子类下使用,若是他的父类不是UITableViewController,Xcode会提示下面的错误: 

“Illegal Configuration: Static table views are only valid when embedded in UITableViewController instances”. 

原型单元格,虽然能够在普通的View Controller中使用,可是不可以在Interface Builder中使用, 

不多会出现有人会想要在一个表中用静态单元格和原型单元格混合起来,目前iOS SDK还不能很好的支持这种方法。 




游戏选择器场景 

在Add Player场景中单击Game的单元格会打开一个新的场景,让你可以从一个列表中选择一个游戏,这意味着咱们须要加入一个新的表格视图,不过不一样的是,咱们此次会使用push到Navigation的栈之中,而不是直接跳转。

拖拉一个新的TableViewController到编辑器中,在Add Player场景中选择一个单元格按住ctrl键拉到新的场景中,建立一个连线,选择Push,以后把新segue的identifier命名为“PickGame”。 

双击导航栏,修改标题为“Choose Game”,修改原型单元格的Style为Basic,修改他的Identifier为“GameCell”,咱们的试图设计就到这里。 
 
新建一个UITableViewController的子类,命名为GamePickerViewController,在storyboard中也要设置好哦。 

首先咱们给这个新的场景一些数据来显示,在GamePickerViewController.h中加入下列变量: 
  1. @interface GamePickerViewController : UITableViewController {  
  2.     NSArray * games;  
  3. }  

以后转到GamePickerViewController.m,在viewDidLoad方法中加入数组的内容。 
  1. - (void)viewDidLoad  
  2. {  
  3.     [super viewDidLoad];  
  4.     games = [NSArray arrayWithObjects:  
  5.              @"Angry Birds",  
  6.              @"Chess",  
  7.              @"Russian Roulette",  
  8.              @"Spin the Bottle",  
  9.              @"Texas Hold’em Poker",  
  10.              @"Tic-Tac-Toe",  
  11.              nil];  
  12. }  

因为在viewDidLoad方法中加载了数组,因此须要在viewDidUnload中卸载之。 
  1. - (void)viewDidUnload  
  2. {  
  3.     [super viewDidUnload];  
  4.     games = nil;  
  5. }  

将模板中的数据源方法修改成以下代码: 
  1. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView  
  2. {  
  3.     return 1;  
  4. }  
  5. - (NSInteger)tableView:(UITableView *)tableView  
  6.   numberOfRowsInSection:(NSInteger)section  
  7. {  
  8.     return [games count];  
  9. }  
  10.   
  11. - (UITableViewCell *)tableView:(UITableView *)tableView  
  12.   cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  13. {  
  14.     UITableViewCell *cell = [tableView  
  15.      dequeueReusableCellWithIdentifier:@"GameCell"];  
  16.     cell.textLabel.text = [games objectAtIndex:indexPath.row];  
  17.     return cell;  
  18. }  

这样咱们就完成了家在数据源的方法,这时候运行这个app,以后在Add Player场景中单击Game栏,就会转入这个视图了,但这时候单击这里的单元格并不会有什么做用。 
 
这时候,因为咱们使用push方式将这个场景推动了Navigation的栈中,因此这时候咱们单击返回按钮就会自动返回到上一级界面。不错吧! 

固然了,若是这个场景不输送任何数据回到上一级场景的话,那他就什么用也没有了,因此咱们要创造一个新的代理器来完成这项任务。在GamePickerViewController.h中加入: 
  1. @class GamePickerViewController;  
  2.   
  3. @protocol GamePickerViewControllerDelegate <NSObject>  
  4. - (void)gamePickerViewController:  
  5.   (GamePickerViewController *)controller  
  6.   didSelectGame:(NSString *)game;  
  7. @end  
  8.   
  9. @interface GamePickerViewController : UITableViewController  
  10.   
  11. @property (nonatomic, weak) id <GamePickerViewControllerDelegate> delegate;  
  12. @property (nonatomic, strong) NSString *game;  
  13.   
  14. @end  

咱们加入了一个代理方法,其中只有一个方法和一个用于乘放目前选择的游戏的名字的属性。 

如今,咱们修改GamePickerViewController.m的开头: 
  1. @implementation GamePickerViewController  
  2. {  
  3.     NSArray *games;  
  4.     NSUInteger selectedIndex;  
  5. }  
  6. @synthesize delegate;  
  7. @synthesize game;  

这些代码新建了一个数组,一个选中项目的整数,而且synthesize了这些项目。 

在viewDidLoad中加入以下代码: 
  1. selectedIndex = [games indexOfObject:self.game];  

选中的游戏名字会设置在self.game中,这里咱们设置咱们在表格中到底选中了哪一个游戏。在这里,在场景加载以前必须首先填充self.game,因为咱们在viewDidLoad以前设置了prepareForSegue这个方法,因此咱们如今这么作没问题。 

修改cellForRowAtIndexPath方法: 
  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  2. {  
  3.     UITableViewCell *cell = [tableView  
  4.      dequeueReusableCellWithIdentifier:@"GameCell"];  
  5.     cell.textLabel.text = [games objectAtIndex:indexPath.row];  
  6.     if (indexPath.row == selectedIndex)  
  7.         cell.accessoryType =  
  8.           UITableViewCellAccessoryCheckmark;  
  9.     else  
  10.         cell.accessoryType = UITableViewCellAccessoryNone;  
  11.     return cell;  
  12. }  

这个方法会在选中的项目的右边加上一个选中的对勾。 

将 didSelectRowAtIndexPath 修改成: 
  1. - (void)tableView:(UITableView *)tableView  
  2.   didSelectRowAtIndexPath:(NSIndexPath *)indexPath  
  3. {  
  4.     [tableView deselectRowAtIndexPath:indexPath animated:YES];  
  5.     if (selectedIndex != NSNotFound)  
  6.     {  
  7.         UITableViewCell *cell = [tableView  
  8.           cellForRowAtIndexPath:[NSIndexPath  
  9.             indexPathForRow:selectedIndex inSection:0]];  
  10.         cell.accessoryType = UITableViewCellAccessoryNone;  
  11.     }  
  12.     selectedIndex = indexPath.row;  
  13.     UITableViewCell *cell =  
  14.      [tableView cellForRowAtIndexPath:indexPath];  
  15.     cell.accessoryType = UITableViewCellAccessoryCheckmark;  
  16.     NSString *theGame = [games objectAtIndex:indexPath.row];  
  17.     [self.delegate gamePickerViewController:self  
  18.      didSelectGame:theGame];  
  19. }  

首先咱们取消以前点击的那一行的选中状态,这将把它的蓝色变会正常的白色,以后将对勾删除掉,以后将对勾放置在刚刚选中的那一行上,最后,咱们把选中的那一行返回给代理。 

如今运行这个app测试一下效果,单击一个game的名字,将会出现一个对勾,单击另外一个行,对勾的位置就会改变,可是返回上一级菜单以后发现咱们的修改没有保存下来,为何?由于咱们尚未将代理真正的连接起来。 

在 PlayerDetailsViewController.h 中,引入 
  1. #import "GamePickerViewController.h"  

以后在 @interface 行以后加入: 
  1. @interface PlayerDetailsViewController : UITableViewController <GamePickerViewControllerDelegate>  

在PlayerDetailsViewController.m加入prepareForSegue方法: 
  1. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender  
  2. {  
  3.     if ([segue.identifier isEqualToString:@"PickGame"])  
  4.     {  
  5.         GamePickerViewController *gamePickerViewController =  
  6.           segue.destinationViewController;  
  7.         gamePickerViewController.delegate = self;  
  8.         gamePickerViewController.game = game;  
  9.     }  
  10. }  

这和咱们以前作过的很类似,可是此次的目标view Controller使game picker场景了,请记住,这个方法必须在GamePickerViewController初始化以后可是尚未加载view的时候调用。 

“game”变量是新的,咱们必须声明他: 
  1. @implementation PlayerDetailsViewController  
  2. {  
  3.     NSString *game;  
  4. }  

咱们使用这个变量来记录到底选择了哪一个Game,咱们得给这个String设置一个默认值,能够用initWithCoder方法来完成。 
  1. - (id)initWithCoder:(NSCoder *)aDecoder  
  2. {  
  3.     if ((self = [super initWithCoder:aDecoder]))  
  4.     {  
  5.         NSLog(@"init PlayerDetailsViewController");  
  6.         game = @"Chess";  
  7.     }  
  8.     return self;  
  9. }  

若是你以前是用过nibs的话,那么initWithCode可能会对你很熟悉,这部分在storyboard是同样的。 

修改 viewDidLoad 方法以下,以便单元格可以显示选中的Game名称: 
  1. - (void)viewDidLoad  
  2. {  
  3.     [super viewDidLoad];  
  4.     self.detailLabel.text = game;  
  5. }  

最后要作的就是执行代理方法: 
  1. #pragma mark - GamePickerViewControllerDelegate  
  2.   
  3. - (void)gamePickerViewController:  
  4.   (GamePickerViewController *)controller  
  5.   didSelectGame:(NSString *)theGame  
  6. {  
  7.     game = theGame;  
  8.     self.detailLabel.text = game;  
  9.     [self.navigationController popViewControllerAnimated:YES];  
  10. }  

这行代码很好懂,我就很少讲了。 

咱们的结束方法将会把选中的游戏的名字加入到新建的Player对象中。 
  1. - (IBAction)done:(id)sender  
  2. {  
  3.     Player *player = [[Player alloc] init];  
  4.     player.name = self.nameTextField.text;  
  5.     player.game = game;  
  6.     player.rating = 1;  
  7.     [self.delegate playerDetailsViewController:self didAddPlayer:player];  
  8. }  

OK,到这里咱们就完成了游戏选择器的场景,不错吧。 
相关文章
相关标签/搜索