iOS 5 故事板入门(3)
原文: http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-2
Segues 介绍
是时候在我们的故事板中加入更多的 ViewController 了。我们将创建一个场景以便用户添加新的玩家到程序中。
拖一个 BarButtonItem 到 Players 场景的导航栏右端。通过属性面板将它的Identifier 改为 Add,这样它就成为一个标准的 + 号按钮。当你点击这个按钮我们将弹出新的模式窗口,你可以在其中输入新玩家的信息。
拖一个 TableViewController 到画布中,放在Players 场景右边。你可以通过双击缩小画布,以便有更多空间进行操作。
选中这个 TableViewController 并把它嵌到一个navigationController (通过菜单“Editor\EmbedIn\Navigation Controller”)。
接下来请注意:选择 + 号按钮,按住 ctrl 键,将它拖向新添加的navigationController。
释放鼠标,在弹出菜单中选择 Modal。在 Players 场景和NavigationController 间会多出一个箭头:
这种连接就叫做 segue(发音:seg-way),表示从一个ViewController 跳到另一个 ViewController。这不同于关系,关系用于表示一个 ViewController 包含另一个ViewController。换种说法,一个 segue 会改变屏幕上的所有东西。当按钮、TableViewCell上的触摸或手势(以及类似的东西)所触发。
segue 的好处是你不需要编写任何呈现新窗口的代码。也不需要将按钮和 IBAction连接起来。如我们刚才所做的,拖一条线将 barButtonItem 和下一个场景连起来就足矣。(如果控件已经有一个 IBAction 连接,则 segue 将覆盖它)。
运行程序,按下 + 号按钮。一个新的 TableView 会弹出!
这是 “modal式”segue。新的窗口完全遮住前面的窗口。用户不能和前面的窗口进行任何交互,除非将弹出的窗口关闭。然后我们将演示“push 式”segue,它将一个新的窗口压入导航控制器的栈中。
这个新的 ViewController 不是很好用——你甚至不能关闭它回到主窗口!
Segue 只是单向的,从 Players 窗口到新窗口。要想返回,必须使用委托模式。首先,我们必须给新场景创建一个类。向项目中添加一个UITableViewController 子类,命名为 PlayerDetailsViewController。
返回故事板编辑器,选中新的 TableViewController ,在Identity 面板中将 Class 改成 PlayerDetailsViewController。我经常忘记这一步,因此我提醒你不要跟我一样。
将它的 title 改为“Add Player” (通过双击导航条).。同时加两个 BarButton Items 到导航条。在属性面板中,设置左边的按钮的 Identifier 为 Cancel,右边的设置为 Done。
然后修改 PlayerDetailsViewController.h:
@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 |
我们定义了一个委托协议,以便当用户点击 Cancel 或者 Done 时,我们能够从AddPlayer 窗口返回到 Players 窗口。
返回故事板编辑器,将 Cancel 按钮和 Done 按钮连接至相应的Action 方法。第一种方法是,按住 ctrl 键,从 BarButtonItem 拖到 ViewController,然后在弹出的菜单中选择合适的action 方法:
在 PlayerDetailsViewController.m 中,加入这俩个方法:
- (IBAction)cancel:(id)sender { [self.delegate playerDetailsViewControllerDidCancel:self]; } - (IBAction)done:(id)sender { [self.delegate playerDetailsViewControllerDidSave:self]; } |
这两个方法分别对应两个 BarButton。这些方法都通知委托对象什么事件被触发了。最终都需要让委托对象去关闭窗口(这不是必须的,但我喜欢这样做。当然,你也可以让AddPlayer 在通知委托对象前或之后自己关闭自己)。
注意,通常委托方法会将调用者的一个引用放在方法的第一个参数(或仅有的一个参数)。在此即 PlayerDetailsViewController 参数。这样可以让委托对象知道是哪个对象发消息给它。
不要忘记合成delegate 属性:
@synthesize delegate; |
我们已经为 PlayerDetailsViewController 定义了一个委托协议,我们需要在某个地方去实现它。显然,这个地方应该是PlayersViewController。因为它负责呈现 AddPlayer 窗口。在 PlayerViewController.h 中加入以下代码:
#import "PlayerDetailsViewController.h" @interface PlayersViewController : UITableViewController <PlayerDetailsViewControllerDelegate> |
在PlayersViewController.m 方法末尾加入:
#pragma mark - PlayerDetailsViewControllerDelegate - (void)playerDetailsViewControllerDidCancel: (PlayerDetailsViewController *)controller { [self dismissViewControllerAnimated:YES completion:nil]; } - (void)playerDetailsViewControllerDidSave: (PlayerDetailsViewController *)controller { [self dismissViewControllerAnimated:YES completion:nil]; } |
这里只是简单地关闭 AddPlayer 窗口。稍后我们会做一些有趣的事情。
iOS5 中出现了新的dismissViewControllerAnimated:completion: 方法。你也可以用之前的老 dismissModalViewControllerAnimated: 方法。但新方法将更加优雅地关闭ViewController(它能给你一个机会在窗口被关闭后能执行额外的代码)。
剩下只有一件事情: Players 窗口需要告诉 PlayerDetailsViewController它将成为它的委托。你可能想,用故事版编辑器在二者间拖一条线不就完了吗?很不幸,这不行。在 segue 中,为了传递数据到下一个 ViewController ,我们仍然必须编写代码。
在PlayersViewController 中添加如下方法:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"AddPlayer"]) { UINavigationController *navigationController = segue.destinationViewController; PlayerDetailsViewController ]; playerDetailsViewController.delegate = self; } } |
prepareForSegue 方法在即将发生 segue 之时触发。新的 ViewController 已经同故事版一起加载了,但它是不可见的,我们必须利用这个方法传递参数给它。(永远不要自己调用prepareForSegue 方法,它由 UIKit 调用以通知你正在触发一个 segue。)
注意 segue 的目标将是 NavigationController,它与我们的BarButtonItem 是关联的。为了得到 PlayerDetailsViewController实例,我们必须从 NavigationController 的 viewControllers 属性中将它找出来。
运行程序,按下 + 号按钮,尝试关闭 AddPlayer 窗口。居然没有效果!
那是因为我们并没有指定 segue 的 identifier 。而在prepareForSegue 方法中我们检查了 identifier 是否为 AddPlayer。在同一个 ViewController 中存在多个 segue 的情况下,建议你通过 identifier 来区分不同的 segue。
在故事版编辑器中,选择该 segue,注意 BarButtonItem 会被加亮显示以便你能清楚地知道是哪个控件触发这个segue。
在属性面板中,将 identifier 设为 AddPlayer。
再次运行程序,触摸 Cancel 或 Done 按钮,现在都能关闭窗口并返回玩家列表了。
注意:也可以从模式窗口中调用 dismissViewControllerAnimated:completion:方法。不一定非要让代理对象来关闭。但如果你这样做,要留意一件事情:你原来使用的[self.parentViewController dismissModalViewControllerAnimated:YES] 不再有效。可以用 self.presentingViewController 替换 self.parentViewController,但这是在 iOS5 之后出现的新属性。
另外,在 segue 的属性面板中,有一个 Transition 字段,在此你可以选择不同的过渡动画。
你净可以尝试你最喜欢的设置。但不要改变 Style 属性。对于这个窗口,它只能是Modal——任何其他选项都会导致程序崩溃!
在本教程中,我们多次使用委托模式。在此我们列出了在两个场景中建立连接的步骤:
- 从源场景的按钮或其他控件创建 segue 到另一个场景。(如果你想呈现一个模式窗口,通常目标对象应该是一个 NavigationController)。
- 指定 segue 的 identifier。(对于同一源场景,identifier必须是唯一的;不同的源场景则可以有重复)。
- 为目标场景创建委托协议。
- 从 Cancel 按钮和 Done 按钮中调用委托方法,对于目标场景想回到源场景的任何地方,你都要这样做。
- 在源场景中实现委托协议。当 Cancel 按钮或 Done 按钮被触摸时,委托对象应当负责关闭目标 ViewController。
- 在源 ViewController 中实现 prepareForSegue 方法,并让目标场景的 delegate=self;
委托模式是必然的,因为没有“反向 segue”这样的东西。当 segue 被触发,它创建的都是新的目标ViewController 实例。你当然可以创建一个 segue 用于从目标返回到源场景,但这太想当然了。
打比方,你可以从 Cancel 按钮创建一个 segue 返回Players 窗口,但却无法关闭 Add Player 窗口回到 Players。除非你创建一个新的 Players 对象。这样你就创建了一个无尽循环直到耗净所有内存。
记住:Segue 只有一个方向,只能用于打开新窗口。要关闭它返回(或者将他从NavigationController 栈中弹出),通常使用委托模式。segue 只适用于源场景,目标 ViewController 根本不知道 segue是什么玩意。
静态Cell
最终的 Add Player 窗口如下图所示:
这是一个分组表视图,但这次我们不需要为它创建一个数据源。我们可以直接在故事版编辑器中设计它,不需要写cellForRowAtIndexPath 方法。这种新特性叫做静态cell。
在 Add Players 场景中选择 tableView,在属性面板修改Content 为 Static Cells。Style 则选择 Grouped,Sections 设置为 2。
Sections 属性一旦改变,编辑器将克隆已有的 section。(你也可以从左边的文档树中选择某个 section 然后复制它)。
对于每个 section,我们只有一行。选择多余的行并删除。选择最上面的section ,通过属性面板,在 Header 字段中输入 Player Name。
拖一个新的 TextField 到这个 section 的仅有的 cell中。移除它的边框,这样你就看不到 textfield 的头和尾。设置字体为 System 17 ,反选Adjust to Fit。
我们将使用 Assistan Editor 创建 textField 的IBOutlet。打开 Assistant Editor,它将自动打开 PlayerDetailsViewController.h。选中 TextField 然后按住 ctrl 拖到 .h 文件中:
放开鼠标键,将弹出窗口:
Name 设置为 nameTextField。点击 Connect,Xcode 将在PlayerDetailsViewController.h 中添加如下属性:
@property (strong, nonatomic) IBOutlet UITextField *nameTextField; |
同时它也自动合成了属性,并在 viewDidUnload 方法中加入语句。
我说过这种方法不能在模板cell 上使用,但在静态 cell 中是可以的。每个静态cell 只有一个实例(与模板 cell 不同,它们不会被复制),因此将它们的 subview 与 ViewController的出口连接是完全可行的。
设置第二个section 的静态 cell 的 Style 为 RightDetail。这是一种标准的 cell 样式。将左边的 Label 文本设置为 Game,accessory 设置为 disclosure indicator。为右边的Label 创建一个出口,命名为 detailLabel。这两个 Label 都是通常的 UILabel 对象。
设计后的 Add Player 窗口最终如下图所示:
使用静态 cell 时,TableViewController 不用数据源。因为PlayerDetailsViewController 类是 Xcode 模板创建的,代码中会存在数据源的模板代码,我们需要删除它。删除在:
#pragma mark - Table view data source |
和:
#pragma mark - Table view delegate |
之间的所有行。这将消除 Xcode 在添加这个类时的一些警告。运行程序,查看静态cell 的效果。我们没有写一行代码——实际上反倒删除了一大堆的代码。
但我们免不了还是要写一些代码。在你想第一个单元格加入 textField 时,你可能会注意到它稍有些不妥,在它的周围会有一些多余的空间。用户看不见textField 的头和尾,因此当他们触摸到这些空处的时候,键盘不会弹出。这个很容易解决,覆盖tableView:didSelectRowAtIndexPath 方法代码如下所示:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { ) [self.nameTextField becomeFirstResponder]; } |
即用户触摸到第一个单元格时,我们激活 textField 焦点(在这个section 中只有一个 cell,因此我们只需要检查 section 的索引即可)。这将自动弹出软键盘。这只是微不足道的工作,但能降低用户的不满。
你还应该在属性面板中将 cell 的 selection style 设置为None,否则用户触摸在文本框附近时,整个行将变成蓝色。
好了,这就是 AddPlayer 窗口的设计了。现在我们要让它真正发挥作用。
iOS 5 故事板入门(3)的更多相关文章
- iOS 5 故事板入门(4)
原文: http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-2 让 AddPlayer 窗口动起来 现在,我们先 ...
- iOS 5 故事板进阶(1)
译自<iOS 5 by tutorials> 在上一章,你已经学习了故事板的基本用法.包括如何向故事板中添加 View Controller,通过 segues 切换 View Contr ...
- iOS 5 故事板进阶(2)
让我们回到游戏排行窗口Ranking.创建一个 UITableViewController子类,命名为 RankingViewController. 编辑 RankingViewController. ...
- iOS: 使用故事板和xib设置按钮圆角方法
使用storyboard如何设置圆角或边框? 通过storyboard的 运行时属性runtime attribute,可以对Button设置圆角或者边框 1.很多人都知道,通常设置一个 Button ...
- iOS系列 基础篇 02 StoryBoard 故事板文件
iOS基础 02 StoryBoard 故事板文件 目录: 1. 故事板的导航特点 2. 故事板中的Scene和Segue 3. 本文最后 在上篇HelloWorld工程中有一个Main.storyb ...
- [IOS 开发] TableView、多个TableViewCell、自定义Cell、Cell上画画(故事板+代码方式)
第一步: //UserTableViewCell.h这里定义第一种Cell #import <UIKit/UIKit.h> @interface UserTableViewCell : U ...
- iOS 非ARC基本内存管理系列 -手把手教你ARC——iOS/Mac开发ARC入门和使用(转)
手把手教你ARC——iOS/Mac开发ARC入门和使用 Revolution of Objective-c 本文部分实例取自iOS 5 Toturail一书中关于ARC的教程和公开内容,仅用于技术交流 ...
- Silverlight & Blend动画设计系列五:故事板(StoryBoards)和动画(Animations)
正如你所看到的,Blend是一个非常强大的节约时间的设计工具,在Blend下能够设计出很多满意的动画作品,或许他具体是怎么实现的,通过什么方式实现的我们还是一无所知.本篇将续前面几篇基础动画之上,详细 ...
- (译)Getting Started——1.2.4 Tutorial:Storyboard(故事板)
该教程是基于你在前面的课程中构建的项目上进行的.学完本教程后,你将使用你前面学到的视图.视图控制器.动作.导航的内容,还会为应用创建一些关键的用户界面,并在场景中添加行为 以下就是本节课的内容: 1. ...
随机推荐
- Log4Net五步走
本文不是教你全面了解log4net,本文只是希望教会你按步就班,照糊芦画瓢般就会用log4net1,引入log4net.dll组件2,建立一个配置文件两种方法,一种是在Web.Config或App.C ...
- Java进阶03 IO基础
链接地址:http://www.cnblogs.com/vamei/archive/2013/04/11/3000905.html 作者:Vamei 出处:http://www.cnblogs.com ...
- 自己用js写的两个日历控件
前一阵写了两个日历控件,做了简单的封装,发出来共朋友们参考. 第一个日历控件,条状的日历. (使用方法:调用initBarTime(id,evn),第一个参数是要渲染div的id,第二个参数是点击日期 ...
- jsp各部分编码的含义
服务器JSP编码 pageEncoding 是jsp文件本身的编码, 第一阶段是jsp编译成.java,它会根据pageEncoding的设定读取jsp,(jsp文件的编码,pageEncoding是 ...
- 一步一步重写 CodeIgniter 框架 (4) —— load_class 管理多个对象实例的思路
我们使用CodeIgniter 框架最主要是想利用其 MVC 特性,将模型.视图分开,并通过控制器进行统一控制.在尝试实现 MVC 模式之前,我们将实现其中一个对程序结构非常有用的技巧,就是 load ...
- ASP.NET中操作SQL数据库
在WebConfig中配置数据库连接字符串,代码如下: <connectionStrings> <add name="ConnectionString&qu ...
- 趋势科技4月移动client病毒报告
2014年4月移动client安全威胁概况 截至2014年4月30日,中国区移动client病毒码1.669.60,大小9,792,484字节,能够检測病毒约221万个.移动client病毒约12万个 ...
- Swift - 使用UI Dynamics给UIKit组件添加重力和碰撞行为
UI Dynamics是UIKit的一个新组成部分,它向iOS中的视图提供了与物理学有关的功能和动画.可以让你向视图中引入力和物理属性,可以让你的视图弹跳,舞动,受重力影响等等. 下面通过样例,演示使 ...
- 用定时器T0的中断控制8位LED闪烁
#include<reg52.h> #define uchar unsigned char #define uint unsigned int uint i; void main() { ...
- 用 Eclipse 下载 Git 仓库中代码
1. 安装 Git 插件 可以按照 通过Eclipse从subversion站点下载源码 中的方法安装,也可以在 Eclipse Marketplace 中搜索 EGit 进行安装(Help --&g ...