iOS 5 故事板进阶(1)

译自《iOS 5 by tutorials》

在上一章,你已经学习了故事板的基本用法。包括如何向故事板中添加 View Controller,通过 segues 切换 View Controller,以及轻松创建定制的表单元格。

在本章,我们将向你展示更多的关于 iOS 5 故事板的新特性。例如如何让用户在应用程序中编辑玩家资料,为场景添加多个 segues,定制 segues,在 iPad 中使用故事板等等。

接下来,用 Xcode 打开你的 Ratings 工程,让我们一起开始吧!

编辑已有的玩家资料

应该让用户能够编辑他们输入的数据。在这一节,我们会修改PlayerDetailsViewControlelr,在原有的增加新玩家的基础上,扩展出编辑玩家的功能。

用右键(ctrl+左键)从Players 的模板cell 拖一条线到与AddPlayer 关联的 Navigation Controller 上,并创建一个 modal segue。命名 segue 为 EditPlayer。这样,在这两个场景间会存在两个segue。

我们通过二者的名称(AddPlayer 和 EditPlayer)来区分两个segue。如果搞不清正在操作哪一个的时候,可以点击 segue 图标,它会以蓝色高亮的形式显示。

PlayersViewController.m中,修改 prepareForSegue 为:

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

{
if ([segue.identifierisEqualToString:@"AddPlayer"])

{
UINavigationController*navigationController =

segue.destinationViewController;

PlayerDetailsViewController *playerDetailsViewController=

[[navigationController viewControllers]objectAtIndex:0];

playerDetailsViewController.delegate = self;

}else if ([segue.identifierisEqualToString:@"EditPlayer"]) {
UINavigationController*navigationController =

segue.destinationViewController;

PlayerDetailsViewController*playerDetailsViewController =

[[navigationControllerviewControllers] objectAtIndex:0];

playerDetailsViewController.delegate = self;

NSIndexPath *indexPath=
[self.tableView indexPathForCell:sender];

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

playerDetailsViewController.playerToEdit = player;

}

}

在 if 语句中,我们增加了检查 segue 是否为 EditPlayer的检测。除了传递了一个 Player 对象给 playerToEdit 属性,其他跟我们在判断 segue 是否为 AddPlayer 中所做的完全相同。

我们使用下句来查找当前触摸的单元格的 IndexPath:

NSIndexPath*indexPath =
[self.tableView indexPathForCell:sender];

prepareForSegue 方法的“sender”参数是触发该 segue的控件的指针。对于名为 AddPlayer 的 segue,sender 是一个 UIBarButtonItem,但对于名为 EditPlayer 的segue,sender 实际上是一个 TableViewCell。我们是在模板 cell 上创建的 segue,也就是说它会被任何模板cell的拷贝所触发。故事板会在场景后面自动完成这一切。

在 PlayerDetailsViewController.h 中增加一个属性声明:

@property(strong, nonatomic) Player *playerToEdit;

然后在 PlayerDetailsViewController.m 中加入:@synthesize playerToEdit;

修改 viewDidLoad 方法:

- (void)viewDidLoad

{
[super viewDidLoad];

if (self.playerToEdit !=nil)

{
self.title =@"Edit Player";

self.nameTextField.text = self.playerToEdit.name;

game = self.playerToEdit.game;

}

self.detailLabel.text =game;

}

如果 playerToEdit 属性不为空,ViewController就不是添加玩家窗口,而是编辑玩家窗口。我们会用 playerToEdit 对象的值填充玩家姓名以及游戏名称。

运行程序,点击一个玩家打开编辑玩家界面。

当然,由于我们实际上还没有完成修改功能,如果此时点击 Done 按钮,结果仍然是向列表中添加一个新玩家而不是修改已有的玩家。我们应该修改其中的逻辑,使其能够修改已有玩家。

首先,在委托协议中定义一个新方法:

PlayerDetailsViewController.h:

- (void)playerDetailsViewController: (PlayerDetailsViewController *)controllerdidEditPlayer:(Player *)player;

然后在 PlayerDetailsViewController.m中修改 done 按钮的 action 方法:

- (IBAction)done:(id)sender

{
if (self.playerToEdit != nil){

self.playerToEdit.name =self.nameTextField.text;

self.playerToEdit.game= game;
[self.delegate playerDetailsViewController:self

didEditPlayer:self.playerToEdit];

else {
Player*player = [[Playeralloc] init];

player.name = self.nameTextField.text;

player.game = game;
player.rating= 1;

[self.delegate playerDetailsViewController:self didAddPlayer:player];

}

}

然后在 PlayersViewController.m中实现这个委托方法:

- (void)playerDetailsViewController: (PlayerDetailsViewController *)controller

didEditPlayer:(Player *)player

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

NSIndexPath *indexPath=

[NSIndexPath indexPathForRow:indexinSection:0];

[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]withRowAnimation:UITableViewRowAnimationAutomatic];

[self dismissViewControllerAnimated:YES completion:nil];

}

这里,我们 reload 了该玩家的单元格以刷新其上的标签,然后关闭编辑窗口。

只需几个小小的改变,我们就可以重用原有的 PlayerDetailViewController类。现在,可以通过两个 segue 到达这个场景,AddPlayer 和 EditPlayer,到底是采取添加的方式还是编辑的方式进入,这取决于哪个segue 被触发。记住,执行每个 seque 时都会创建全新的目标 Viewcontroller 对象,如果你先添加玩家又编辑该玩家,你实际上是在同不同的 Viewcotnroller对象进行交互。

我反复重复这一点,prepareForSegue 在 viewDidLoad方法之前调用。恰恰是利用这一点,我们在 prepareForSegue 方法中设置了 playerToEdit 属性。在 viewDidLoad 方法中我们就可以获取playerToEdit 并用于渲染标签中的文本。

 

注意:在目标ViewController 上,并没有一个叫做 didPerformFromSeque 的方法。事实上,ViewController 根本不知道 segue。要告诉目标ViewController 它是被 segue 所触发的,只能通过 prepareForSegue 方法——要么设置它的某个属性,要么调用它的某个方法。你可以重载目标ViewController的属性 setter 方法,例如:

- (void)setPlayerToEdit:(Player*)newPlayerToEdit

{
if (playerToEdit!= newPlayerToEdit) {
playerToEdit= newPlayerToEdit;

}

}

// 额外的设置 // ...
self.invokedFromSegue= YES;

评分窗口

这个程序叫做“Rating”,但除了显示几个星形图标外根本就没打分的功能。现在,我们将增加一个ViewController 用于给玩家评分:

拖一个 ViewController 到画布,放到AddPlayer 下面。一个普通的 ViewController就可以,不是 UITableViewController。

出现一个问题,我们想通过玩家列表来调用这个新的评分窗口,但单元格上已经有个segue 用于打开玩家编辑窗口。我们必须用一种方法来区分这两个操作。我们将采用的办法是:用触摸单元格来弹出评分窗口,而用触摸打开细按钮来弹出玩家编辑窗口。

首先选中Players 中的模板cell,修改它的 accessory 属性为Detail Disclosure,这样它的 accessory 会变成一个蓝色的小圆按钮。

删除名为 EditPlayer 的 segue。我们本想从 detaildisclosure 按钮创建一个新的 segue 添加玩家/编辑玩家场景。不幸的是,故事板编辑器并不支持这种操作。我们只能在 ViewController 上创建一个指向它自身的segue 然后在代码中定制它。

从 dock 上的 ViewController 图标上用右键拖一条线到NavigationController ,创建一个 Modal segue,命名为 EditPlayer。注意,这个 segue 是连接到 Players 窗口自身的,而不是连接到它里面的某个控件。

从模板 cell 创建一个 segue 到新加的 ViewController,命名为RatePlayer。现在,在 Players 窗口上有 3 条 segue。双击 Navigation Bar,将新窗口的标题设置为 Rate Player。

回到 disclosure 按钮的问题上来。你应该知道 TableView有一个特殊的委托方法用于处理 diclosure 按钮事件的。我们将在 PlayersViewController.m 中使用这个方法来手动触发EditPlayer segue。

- (void)tableView:(UITableView*)tableView

accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath

{
[self performSegueWithIdentifier:@"EditPlayer" sender:indexPath];

}

通过故事板(及其包含的 NavigationController)加载PlayerDetailsViewController 就是如此简单。当然,这仍然会调用 prepareForSegue。我们需要稍微修改一下这个方法。之前,sender参数指向了触发 segue 的 UITableViewCell,但现在没有 TableViewCell,而是一个 NSIndexPath(因为在 performSegueWithIdentifier方法中我们传递的只是一个 IndexPath)。

在 prepareForSegue 方法中,找到这行:

NSIndexPath*indexPath = [self.tableView indexPathForCell:sender];

修改为:

  NSIndexPath *indexPath= sender;

当你通过 performSegueWithIdentifier 方法手动触发segue 时,你可以发送任何想要的参数。

我只所以传递一个 IndexPath 参数,是为了省事(你也可以发送一个Player 对象)。

运行程序,触摸dislcosure 按钮,弹出玩家编辑窗口(模式)。触摸表格行,则转到评分窗口(Push)。

继续设计给玩家打分窗口。添加一个 UIViewController 子类到项目中,命名为RatePlayerViewController(记住,它仅仅是一个常规的 ViewController,不是一个 TableViewController)。

在 Identity 面板中设置Rate Player窗口的类为 RatePlayerViewController。这是我经常容易忘记的步骤,因此我不得不花两分钟去奇怪为什么窗口不显示,到最后才想起我忘记做什么了。

编辑 RatePlayerViewController.h的内容:

@class RatePlayerViewController;@class Player;

@protocolRatePlayerViewControllerDelegate <NSObject>

- (void)ratePlayerViewController:

(RatePlayerViewController *)controller

didPickRatingForPlayer:(Player *)player;

@end

@interfaceRatePlayerViewController : UIViewController

@property(nonatomic, weak)
id <RatePlayerViewControllerDelegate>delegate;

@property(nonatomic, strong) Player *player;

- (IBAction)rateAction:(UIButton*)sender;

@end

看起来应该很熟悉。我们再次使用了委托模式,用于返回给源 ViewController。

RatePlayerViewController.m的顶部内容如下。只是导入与属性合成语句:

#import"RatePlayerViewController.h"

 #import "Player.h"

@implementationRatePlayerViewController @synthesizedelegate;

@synthesizeplayer;

修改 viewDidLoad 方法为:

- (void)viewDidLoad

{
[super viewDidLoad];

self.title = self.player.name;

}

用所选玩家的姓名取代原来的导航栏标题(Rate Player)。

值得注意的是 rateAction 方法:

- (IBAction)rateAction:(UIButton*)sender

{
self.player.rating =sender.tag;

[self.delegate ratePlayerViewController:self

didPickRatingForPlayer:self.player];

}

 

设置 Player 对象的 rating 属性,然后调用委托方法。rating属性是使用 sender.tag 进行赋值的,而 sender 实际上是 UIButton。我们会在 ViewController 上加入 5 个 UIButton——1颗星,2颗星……等等——每个的tag 被设置为 1 到 5。所有的按钮都使用同一个 action 方法。这是一种非常简单的办法。

向 Rate Player 窗口拖入 5 个按钮,并且进行适当的布局。

按钮使用的图片已经在项目中了(Images 文件夹下),1Star.png,2Star.png……等等。每个按钮的Touch Up Inside 事件与 rateAction 方法进行连接。设置它们的tag属性为 1-5。tag 属性的值与按钮的星数相对应。

我也将场景的背景色设置为淡灰色,这样按钮图片会显得更显眼一些。

这个窗口用不着在导航栏中放 Cancel 按钮和 Done 按钮,因为它是通过Push 的方式弹出的。

最后一步,设置 delegate 属性,以便这些按钮能够发送消息给委托对象。修改

 PlayersViewController.h:

#import"RatePlayerViewController.h"

@interfacePlayersViewController : UITableViewController<PlayerDetailsViewControllerDelegate,RatePlayerViewControllerDelegate>

修改PlayersViewController.m:
#pragma mark -RatePlayerViewControllerDelegate

- (void)ratePlayerViewController: (RatePlayerViewController *)controller

didPickRatingForPlayer:(Player *)player

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

NSIndexPath *indexPath=

[NSIndexPath indexPathForRow:indexinSection:0];

[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]withRowAnimation:UITableViewRowAnimationAutomatic];

[self.navigationController popViewControllerAnimated:YES];

}

当 Rate Player 窗口关闭,玩家对象被修改,我们再次刷新了 TableViewCell的显示。当然,别忘了还有 prepareForSegue 方法:

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

// ...原来的代码...

else if ([segue.identifier isEqualToString:@"RatePlayer"]) {
RatePlayerViewController *ratePlayerViewController=

segue.destinationViewController;

ratePlayerViewController.delegate = self;

NSIndexPath*indexPath = [self.tableView indexPathForCell:sender];

Player *player= [self.players objectAtIndex:indexPath.row]; ratePlayerViewController.player = player;

}

}

由于 Players 窗口有 3 个 segue,因此有 3 个 if 语句。

运行程序,检验效果。

手势

前面我们曾经忽略了程序的第2个 tab 窗口。现在让我们在其中添加一些东西。项目中有一个类叫做ViewController,那是 Xcode 模板为我们生成的,我们一直没有用到。将 ViewController 重命名为GestureViewConroller。

在故事版中,选择 ViewController ,将它连接到第 2 个tab 并将它的类设为 GestureViewController。

拖一些 Label 和一个 NavigationBar 到上面,最终如下图所示:

由于这个场景并不会 Push 新的窗口,因此我们就不将它嵌到导航控制器中了。只需要放一个导航栏到顶端就够了。

正如 Label 中 文本所示,我们将添加两个手势。向右扫,我们将弹出列有最佳玩家(五星)列表的窗口;双击,弹出最差(1星)玩家列表窗口。我们需要一个TableViewController 来做这个。

从Library 中拖一个导航控制器到画布中。这会创建出两个新的场景:一个Navigation Controller 和一个与之关联的 Root ViewContorller。我们并不需要 Root View Controller,请删除它。

拖一个 TableViewController 到新的导航控制器旁边。右键,从导航控制器拖一条线到TableViewController,然后选择“Relationship - rootViewController”。为了方便操作,你可以对画布中的场景进行调整。

我们准备用新的 TableViewController 作为游戏排行榜窗口。通过它的NavigationItem 设置它的标题,这样我们就能在一堆场景中将它一眼识出。现在,故事板已经包含了太多的东西。

回到手势窗口,通过手势触发一个 segue 其实非常简单。在对象库面板中有几种不同的手势识别器,拖一个Swipe Gesture Recognizer 到 Gesture 窗口。这会在 Dock 中加入一个手势识别器图标:

在这个图标上右键,拖到旁边的 Navigation Controller,选择“Modalsegue”。将segue 命名为 BestPlayers。从 Library 中拖一个 Tap Gesture Recoginizer ,同样创建一个名为WorstPlayers 的 segue。在 Tap Gesture Recognizer 的属性面板中,设置 number of taps 属性为2,以便侦测双击动作。

运行程序,执行手势,要么 segue 不会触发,要么程序崩溃 :]



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值