iOS 5 故事板进阶(2)

让我们回到游戏排行窗口Ranking。创建一个 UITableViewController子类,命名为 RankingViewController。

编辑 RankingViewController.h内容如下:
@interface RankingViewController: UITableViewController

@property(nonatomic, strong) NSMutableArray*rankedPlayers;

 - (IBAction)done:(id)sender;
@end

 

在故事板编辑器中,将 Ranking 场景的类设置为 RankingViewController。加一个Done 按钮到它的 navigationBar 并将它的 action 连接到 done action 方法。

提示:你可以简单地用右键从 Done 按钮拖到状态栏,这将以 ViewController作为连接终点。

删除 Table View 中的模板 cell。对于这个场景我们会用传统方式创建cell。传统创建单元格的方式仍然是可用的,你甚至可以将它和模板cell 一起使用。表格中的一部分 cell 使用模板 cell ,而另一部分使用传统的方式创建。(和静态cell 混合使用也是可以的,不过也需要更多的技巧)。

最终 Ranking 窗口设计效果如下:

iOS 5 故事板进阶(2)_第1张图片

注意:在写至本章为止,iOS 中存在一个 bug,如果向 TabBarController 所包含的 Viewcontroller 中添加手势识别器,会导致程序崩溃。因此,你可以通过按钮来触发segue。

实现 RankingViewController.m中的数据源方法:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

{
return 1;

}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInt

eger)section

{
return [self.rankedPlayers count];

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPat h:(NSIndexPath *)indexPath

{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {

cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:CellIdentifier];

}

Player *player= [self.rankedPlayersobjectAtIndex:indexPath.row];

 cell.textLabel.text = player.name;
cell.detailTextLabel.text = player.game;

return cell;

}

当然,我们需要导入 Player 类并合成 rankedPlayers 属性。

#import"Player.h"
@implementation RankingViewController

@synthesizerankedPlayers;

最后,在done action 方法中:

- (IBAction)done:(id)sender

{
[self dismissViewControllerAnimated:YES completion:nil];

}

该 ViewController 不需要实现delegate 方法。我们并不需要向Ranking 的委托对象返回任何东西。因此在用户触摸 Done 按钮时,仅仅是将它自己解散。

运行 app。你可以打开和关闭 Ranking 窗口,虽然它只是一个空白窗口。我们需要让它显示经过分级的玩家列表。

GesturesViewController.h中添加属性:

 @property (nonatomic, strong)NSArray *players;

GesturesViewController.m中合成它:

 @synthesize players;

同时导入两个头文件:

#import"RankingViewController.h"

 #import "Player.h"

在 prepareForSegue 方法中为两个手势配置不同的segue:

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

{
if ([segue.identifier isEqualToString:@"BestPlayers"]) {
UINavigationController *navigationController=

segue.destinationViewController;

RankingViewController *rankingViewController =

[[navigationControllerviewControllers] objectAtIndex:0];

rankingViewController.rankedPlayers = [self playersWithRating:5];

rankingViewController.title = @"BestPlayers";

} else if ([segue.identifier isEqualToString:@"WorstPlayers"]) {
UINavigationController *navigationController=

segue.destinationViewController;

RankingViewController *rankingViewController =

[[navigationControllerviewControllers] objectAtIndex:0];

rankingViewController.rankedPlayers = [self playersWithRating:1];

rankingViewController.title = @"WorstPlayers";

}

}

对于两个 segue,我们首先获取位于 segue 终点的 NavigationController,进而获取RankingViewcontroller 实例并设置它的 rankedPlayers 和 title 属性。

playersWithRating 方法实现如下:

- (NSMutableArray *)playersWithRating:(int)rating

{
NSMutableArray*rankedPlayers =

[NSMutableArray arrayWithCapacity:[self.players count]];

for (Player *player in self.players) {
if (player.rating== rating)

[rankedPlayers addObject:player];

}

return rankedPlayers;

}

for循环遍历玩家列表,将指定界别的玩家添加到一个数组中。

现在的问题是,GesturesViewController 从哪里获得玩家列表?当然是从AppDelegate。

在 AppDelegate.m 中导入头文件:

#import"GesturesViewController.h"
在 didFinishLauchingWithOptions: 方法中底部加入代码:

- (BOOL)application:(UIApplication*)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

// ...已有的代码...

GesturesViewController *gesturesViewController= [[tabBarController viewControllers] objectAtIndex:1];

gesturesViewController.players= players;

return YES;

}

现在所有的模型数据已准备妥当,你可以运行程序了。向右扫,显示所有 5 星玩家,双击显示所有1 星玩家。

但工作还未完成。现在,我想把 Ranking 场景和 RatePlayer 场景连接起来。

在故事板中,用右键,从 Ranking 场景拖一个 Push segue 到Rate Player,命名为 RatePlayer。现在有两个 segue 连接到了 Rate Player 场景,名字都叫做 RatePlayer,但它们的源场景不同。

对于 RatePlayerViewController,它不关心会收到多少segue,以及 segue 的另一边(源场景)是哪个类。它仅仅是需要通过它的 player 属性接收一个 Player 对象,然后过委托对象返回调用它的 ViewController。事实上,它不知道(也不关心)调用它的segue。

如果不使用委托而使用硬编码的方式,很难从RankingViewController 或其他场景跳转到 RatePlayerViewController。问题是RatePlayerViewController 根本不知道 PlayersViewController 或者 RankingViewController。它只能向实现了RatePlayerViewControllerDelegate 的对象发送消息。委托模式使你的代码模块化、可重用,避免难以预料的后果。

由于我们没有在 Ranking 窗口中使用模板cell,我们可以用编码的方式触发segue。在 RankingViewController.m 中修改 didSelectRowAtIndexPath 方法为:

- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath

{
Player *player= [self.rankedPlayersobjectAtIndex:indexPath.row];

[self performSegueWithIdentifier:@"RatePlayer" sender:player];

 }

我们首先检索出对应的 Player 对象,然后将它放在 performSegueWithIdentifier方法中传递。这可能跟 sender 的原义有点不一致,但这种方式是完全可行的。

当然,我们也需要实现 prepareForSegue 方法:

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

{
if ([segue.identifier isEqualToString:@"RatePlayer"])

{
RatePlayerViewController *ratePlayerViewController=

segue.destinationViewController;

ratePlayerViewController.delegate = self;

ratePlayerViewController.player = sender;

}

 }

RankingViewController.h中,加入 import 语句并声明要实现的协议:

#import"RatePlayerViewController.h"
@interface RankingViewController: UITableViewController

<RatePlayerViewControllerDelegate>
最后在RankingViewController.m 中实现协议方法。其实仅仅是关闭界面:

#pragmamark - RatePlayerViewControllerDelegate

- (void)ratePlayerViewController: (RatePlayerViewController *)controller

didPickRatingForPlayer:(Player *)player

{
[self.navigationController popViewControllerAnimated:YES];

}

运行 app。你现在已经可以通过 Ranking 窗口为玩家打分了。

注意:可以在自加速计和陀螺仪的事件或其他故事板编辑器中不能显示的事件中用performSegueWithIdentifier 方法触发 segue,想象力是你唯一的限制(当然也别太挑战用户的底线)。

为了让程序显得更加合理,一旦玩家级别被修改,我们应该将玩家从Ranking 窗口中移除,因为很可能该玩家不再是最佳(5星)或者最差(1星)玩家了。

在 RankingViewController.h 中加入一个新属性:

@property(nonatomic, assign) int requiredRating;

罗列最佳玩家时,将该属性设置为 5,在罗列最差玩家时则设置为1。

RankingViewController.m 中合成该属性:

 @synthesize requiredRating;

修改RatePlayerViewControllerDelegate 的协议方法:

- (void)ratePlayerViewController: (RatePlayerViewController *)controller

didPickRatingForPlayer:(Player *)player

{
if (player.rating != self.requiredRating)

{
NSUInteger index = [self.rankedPlayers indexOfObject:player];

[self.rankedPlayers removeObjectAtIndex:index];

NSIndexPath*indexPath =
[NSIndexPath indexPathForRow:indexinSection:0];

[self.tableView deleteRowsAtIndexPaths: [NSArrayarrayWithObject:indexPath]

withRowAnimation:UITableViewRowAnimationFade];

}

[self.navigationController popViewControllerAnimated:YES];

}

一但玩家级别被修改,我们将该玩家从数组和列表中删除。

在 GesturesViewController中,我们必须设置 requiredRating 属性:

- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender raywenderlich.com

{
if ([segue.identifier isEqualToString:@"BestPlayers"])

{

rankingViewController.requiredRating = 5;

}

else if ([segue.identifier isEqualToString:@"WorstPlayers"])

{

rankingViewController.requiredRating = 1;

}

}

测试程序运行的效果。

提示: 我们也应该刷新 Players 窗口,这个工作留给读者自己去完成。

 


你可能感兴趣的:(iOS 5 故事板进阶(2))