原文地址:http://www.raywenderlich.com/50310/storyboards-tutorial-in-ios-7-part-2






把 PlayerDetailsViewControl
@class PlayerDetailsViewControl |
这里我们定义了一个委托协议,好让新的场景可以返回上一个场景,并且添加了cancel和done按钮的两个点击事件。
切换到故事版,点击新建的表视图导航栏上那个done按钮,按住ctrl进行拖拽,拖拽箭头如下图所示:
选择Sent Actions栏里的done:事件,同样,cancel按钮也如此步骤,选择cancel:事件。
到 PlayerDetailsViewControl
- (IBAction)cancel:(id)sender { [self.delegate playerDetailsViewControl |
这是对应两个按钮的点击事件方法。我们可以用它来调用代理,通过代理方法来关闭当前的场景或者视图。它是由委托关闭屏幕。(这里默认的是必须实现方法,你要不喜欢可以改成选用,在协议方法前面加上@optional字段。或者你也可以通过当前控制器自行关闭,并且在关闭前后通知代理)。
注释:在这里代理方法只有一个对象作为参数,在这种情况下,PlayerDetailsViewControl
之前,我们已经给了PlayerDetailsViewControl
#import "PlayerDetailsViewControl |
实现代理方法
#pragma mark - PlayerDetailsViewControl |
目前,这些委托方法只是关闭当前视图控制器。以后会让他们做更多有趣的事情。
现在还差一件事情没做了,设置PlayersViewController为PlayerDetailsViewControl
将以下方法添加到PlayersViewController.m:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"AddPlayer"]) { |
prepareForSegue这个方法在新视图从storyboard上加载的时候就会被处罚,但是它是不可见,需要手动实现,所以我们可以用它来设置代理。
注意:一般情况下不会调用prepareForSegue:方法。这是一个从UIKit的一条消息,让你知道,segue被触发了。
这个segue的目的地是导航控制器,因为那是你连接到栏按钮项目。要获得PlayerDetailsViewControl
运行应用程序,按下+按钮,并尝试关闭添加播放器画面。它仍然无法正常工作!
那是因为你没有给segue标识符。从prepareForSegue代码检查该标识符(“AddPlayer”)。因此建议经常做这样的检查,因为你可能有多个segue到一个视图控制器,你就需要他们各自的区别开来。
要解决此问题,请打开Main.storyboard并点击玩家屏幕和导航控制器之间的segue。请注意,栏按钮项目现在亮起,这样你就可以很容易地看到哪些控制触发此SEGUE。
在属性检查器(Attributes inspector)中,标识符(Identifier)设置为AddPlayer:
再次运行程序,这下是不是就可以正常关闭弹出的模态视图啦!
注意:这本来完全可以调用dismissViewControllerAni
选择一个你喜欢的过场动画,但是记住千万别改变风格(style)设置,因为这里的链接是模态关系 - 改变会导致应用程序的崩溃,切记!
我们将会在本教程中使用几次委托协议,下面是个核对列表供你检查两个场景之间的链接关系:
- 通过一个按钮或者其他控件创建一个sugue来链接源场景到目标场景。 (如果你是新建模态视图,这时目标场景将是一个导航控制器。)
- 给segue的唯一标识符。 (源场景必须有唯一的标示符,不同的场景可以使用相同的标识符。)
- 为目标场景创建一个委托协议。
- 调用取消和完成按钮的委托方法,你的目的地的场景需要与源场景进行通信(两个视图控制器之间的通讯)。
- 源场景实现委托协议。当取消或完成按钮被按下,它应该关闭该目标视图控制器。
- 在源视图控制器实现prepareForSegue:方法,并且把目标视图控制器的代理设置为源视图控制器(eg:playerDetailsViewControl
ler.delegate = self)。
委托是必须的,因为至少在iOS5没有东西可以作为一个“反向segue”。当segue被触发,当segue被触发的时候总是会为目标视图控制器创建一个实例。你当然可以做一个反向的segue从目的到源,但可能不会做你所期望的。(这段搞不懂,谷歌翻译的)
如果你是为表演者场景创建一个segue回来的取消按钮,例如,那么这将不会关闭添加表演者场景,并返回到表演者,但它创造的表演者场景的新实例。你这样一遍一遍的创建新的视图控制器会导致内存溢出。(也是没看懂,没办法翻译,针对上面没看懂的这两段,我没管,看下面这个提示就够了!)
请记住:segue只有一条路可走,它们只是用来进入一个新的场景。要返回您之前的场景(或从导航控制器堆栈中弹出它),通常是用delegate。segue仅由源控制器使用。目标视图控制器甚至不知道它是被segue调用。
静态单元格
在本章结束的时候你的添加表演者场景会像下面这样:
这是一个分组表视图,当然,你不必为此表格创建一个数据源。您可以在Interface Builder中直接设计它 - 没必要单独为它写一个cellForRowAtIndexPath方法,这种称为静态单元格。
在添加表演者控制器里选择表视图,在属性检查器(Attributes inspector)里改变content为static cells。把style改变为Grouped,并且设置两个组(两个section)。
当您更改section的属性值时,编辑器将复制现有的部分。(你也可以自己手动复制)
完成后的场景里每个section里只有一个cell,多余的就把它们删了吧。(别说你不会删)
选择Table View Section,这个可能不是很好点中,那你就在编辑器或者导航条上选吧,这样就妥妥的了,像这样:



@property (weak, nonatomic) IBOutlet UITextField *nameTextField; |
现在,点击第二个section里的静态单元格,把Style改为Right Detail,这是一个标准的单元格样式,点击cell里左边的label,把label内容改为Game并且把accessory风格改为Disclosure Indicator。(不知道是版本问题还是什么,我在xcode5.0.2上发现改了label的内容什么都没有,后来发现字体大小为0,次奥,以前xib没这情况啊,继续。。。)
把cell右边的label也改了,内容改为Detail,然后重复刚才拖拽text field的步骤一样,为这个label新建一个接口并命名为detailLabel,就像这样:
添加播放器页面的最终看起来像这样:
提示:现在这个设计的屏幕都为iPhone5的,屏幕高568个像素点,相对于以前的iPhone机型的4英寸屏幕的高度为480个像素点。您可以使用最左边的按钮,从小事浮动面板,坐落在画布的底部这两种形式的因素之间进行切换。
很明显,你的应用程序应该正常工作与两个屏幕大小。您可以使用自动调整大小口罩或从的iOS 6新的自动布局技术实现这一点。对于评级的应用程序,你不必做任何幻想。它仅使用表格视图控制器,它们会自动调整大小以适合在iPhone5的额外的屏幕空间。
返回到添加表演者页面。当您使用静态的单元格,你的表视图控制器不需要一个数据源。由于您使用的Xcode的模板来创建PlayerDetailsViewControl
打开PlayerDetailsViewControl
#pragma mark - Table view data source
|
这样删除之后你运行来试试看是不是可以工作了,对于静态单元格你基本可以不用写任何代码,除了控制代码之外。
这里还有个问题就是text field没有整个覆盖在cell上面,你点击cell边缘的时候键盘是不会弹出的。
为了避免这种情况,你应该让第一行单元格里调出键盘。这是很容易做到,只需添加一个这个方法didSelectRowAtIndexPath:实现如下:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) { [self.nameTextField becomeFirstResponder]; } } |
这只是说,如果用户点击第一个单元格,应用程序应该激活text filed。我们用代码调整当点击第一个section里的cell时候,应该激活text field,这是为了更好的用户体验。
还有,我们应该选择cell,把selection的风格从blue改变为none,这样就没有了之前那样点击text field没有覆盖到的cell部分的时候出现高亮暗影而影响用户体验了。
好吧,这是添加表演者场景的设计。现在,让我们真正使其发挥作用。
让添加表演者视图工作吧
现在你会忽略了游戏的行,只是让用户输入玩家的名字。
当用户按下Cancel按钮,屏幕应该关闭,任何数据都将丢失。这部分已经没问题了。委托(在玩家屏幕)接收“并取消”的消息,并简单地反馈给视图控制器。
当用户按下Done(完成),但是,你应该创建一个新的表演者对象,并填写其属性。那么你应该告诉代理已经添加了一个新的,使它好更新自己的视图。
现在PlayerDetailsViewControl
#import "Player.h"
|
接着改变done:方法:
- (IBAction)done:(id)sender { Player *player = [[Player alloc] init]; player.name = self.nameTextField.text; player.game = @"Chess"; player.rating = 1; [self.delegate playerDetailsViewControl |
done:方法现在创建一个新的player实例,并把它发送给该委托。委托协议目前并无此方法,所以PlayerDetailsViewControl
@class Player; |
所以在新的代理方法里面要作一些新的改变:
- (void)playerDetailsViewControl |
这里的意思就是把新建的player对象加到players数组里去,然后通知表视图(table view)新增加了一行,因为表视图(table view)和数据源(data source)数据必须同步。
你可以只执行[self.tableView reloadData]这个方法,但是为了给表视图插入新的行有动画效果,UITableViewRowAnimationA
现在试试吧,你应该能在列表里看到新加入的行了!
如果你想知道这些故事板的性能表现,那么你应该知道,一次加载整个故事板是不是一个大问题。故事板并不是马上实例化所有的视图控制器,只有初始视图控制器的时候才会。因为你的初始视图控制器是一个标签栏控制器,它包含两个视图控制器也被加载。
然而别的控制器也并不实例化,直到你连线桥接给他们。当您关闭这些视图控制器,他们会立即释放,所以只有积极使用视图控制器是在内存中,就像如果你使用独立的nib一样。
让我们实践来看看。在PlayerDetailsViewControl
- (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { NSLog(@"init PlayerDetailsViewControl |
你重写的initWithCoder:和dealloc的方法,使他们能在Xcode的调试窗口打印信息。现在再次运行该应用程序,并打开添加选手画面(Add Player页面)。你应该能看到,这个时候视图控制器没有分配到内存。
当你关闭添加播放器屏幕(Add Player页面),按点击取消或完成,你应该看到dealloc内的消息被打印。如果你再次打开这个页面,你也应该看到的initWithCoder:方法的响应。这应该向你保证,视图控制器是按需加载而已,就像他们如果你是手工加载nib一样。
关于静态单元格:他们只工作在UITableViewController。虽然界面生成器可以让你把它们作为一个Table View对象添加到一个普通的UIViewController中,但这将不会在运行时正常工作。这样做的原因是,UITableViewController中提供了一些额外的特殊照顾的数据源的静态单元格。 Xcode中甚至会阻止您编译这样一个项目,并显示错误消息:“非法配置:在嵌入的UITableViewController情况下的静态表视图才有效”。
原型单元格,另一方面,只是正常工作中,你放置一个表视图到普通视图控制器内。虽然是在nib下工作。目前,如果你想使用原型单元格或静态单元格,你必须使用一个故事板。
它不是不可想象的,你可能想,结合静态单元格和常规动态单元格的单表视图,但这不是很好被SKD支持。如果你需要在你的app中加入这些,你可以看看这个论坛:this post on the Apple Developer Forums
转载自:http://blog.sina.com.cn/s/blog_5c5c87d80101dzyh.html