译自《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 ];

playerDetailsViewController.delegate = self;

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

segue.destinationViewController;

PlayerDetailsViewController*playerDetailsViewController =

[[navigationController];

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

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

}

}

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

- (void)playerDetailsViewController: (PlayerDetailsViewController *)controller

didEditPlayer:(Player *)player

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

NSIndexPath *indexPath=

[];

[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=

[];

[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 不会触发,要么程序崩溃 :]

iOS 5 故事板进阶(1)的更多相关文章

  1. iOS 5 故事板进阶(2)

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

  2. iOS 5 故事板入门(4)

    原文: http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-2 让 AddPlayer 窗口动起来 现在,我们先 ...

  3. iOS 5 故事板入门(3)

    原文: http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-2 Segues 介绍 是时候在我们的故事板中加入更 ...

  4. iOS: 使用故事板和xib设置按钮圆角方法

    使用storyboard如何设置圆角或边框? 通过storyboard的 运行时属性runtime attribute,可以对Button设置圆角或者边框 1.很多人都知道,通常设置一个 Button ...

  5. iOS系列 基础篇 02 StoryBoard 故事板文件

    iOS基础 02 StoryBoard 故事板文件 目录: 1. 故事板的导航特点 2. 故事板中的Scene和Segue 3. 本文最后 在上篇HelloWorld工程中有一个Main.storyb ...

  6. [IOS 开发] TableView、多个TableViewCell、自定义Cell、Cell上画画(故事板+代码方式)

    第一步: //UserTableViewCell.h这里定义第一种Cell #import <UIKit/UIKit.h> @interface UserTableViewCell : U ...

  7. [Swift实际操作]八、实用进阶-(8)使用performSegue在故事板页面之间进行数据传递

    本文将演示故事板页面之间的数据传递.首先在一个空白项目中,打开项目自带的故事板文件(Main.storyboard).故事板中已经拥有了一个视图控制器,点击选择该视图控制器.然后依此点击[Editor ...

  8. [Xcode 实际操作]九、实用进阶-(22)Storyboard故事板的常用布局结构

    目录:[Swift]Xcode实际操作 本文将演示如在使用故事板搭建项目时,常用的一种故事板布局结构. 在项目导航区,打开故事板文件[Main.storyboard] 当前故事板中只有一个视图控制器控 ...

  9. [Xcode 实际操作]九、实用进阶-(23)多个Storyboard故事板中的页面跳转

    目录:[Swift]Xcode实际操作 本文将演示多个Storyboard故事板中的页面跳转. 使用快捷键[Command]+[N]创建一个新的故事板文件. (在项目文件夹[DemoApp]上点击鼠标 ...

随机推荐

  1. linux RCU锁机制分析

    openVswitch(OVS)源代码之linux RCU锁机制分析 分类: linux内核  |  标签: 云计算,openVswitch,linux内核,RCU锁机制  |  作者: yuzhih ...

  2. 778A String Game

    A. String Game time limit per test 2 seconds memory limit per test 512 megabytes input standard inpu ...

  3. 洛谷2971 [USACO10HOL]牛的政治Cow Politics

    原题链接 假设只有一个政党,那么这题就退化成求树的直径的问题了,所以我们可以从此联想至\(k\)个政党的情况. 先处理出每个政党的最大深度,然后枚举每个政党的其它点,通过\(LCA\)计算长度取\(\ ...

  4. UI设计师如何提升审美?

    不得不承认,作为一名设计师,独特的审美能力是设计的灵感所在,不过很多刚刚从事UI设计的人,审美能力真的非常的一般,所以心中难免有这样的疑问,我的审美能通过后天的努力提升吗?关于这点,可以非常肯定的说, ...

  5. Java中的四种内部类

    Java中有四种内部类: 成员内部类:定义在另一个类(外部类)的内部,而且与成员属性和方法平级,故称成员内部类.类比于外部类的非静态方法,如果用static修饰就变成了静态内部类 静态内部类:使用st ...

  6. Web 文件上传方面的安全问题

    一. 文件上传漏洞与WebShell的关系 文件上传漏洞是指网络攻击者上传了一个可执行的文件到服务器并执行.这里上传的文件可以是木马,病毒,恶意脚本或者WebShell等.这种攻击方式是最为直接和有效 ...

  7. 数据库MySQL 之 索引原理与慢查询优化

    数据库MySQL 之 索引原理与慢查询优化 浏览目录 索引介绍方法类型 聚合索引辅助索引 测试索引 正确使用索引 组合索引 注意事项 查询计划 慢查询日志 大数据量分页优化 一.索引介绍方法类型 1. ...

  8. angular2在双向数据绑定时[(ngModel)]无法使用的问题

    angular2在双向数据绑定时[(ngModel)]无法使用,出现的错误是: Can't bind to 'ngModel' since it isn't a known property of ' ...

  9. 模板 Template

    package ${enclosing_package}; import java.io.IOException;import javax.servlet.ServletException;impor ...

  10. m序列生成电路

    m序列