在开始这章之前,先做个说明,从这篇开始,我所使用的xcode更新成了最新的版本,版本是4.6.1(4H512),如下:
大家可以打开自己电脑上的App Store,然后搜索xcode,第一个出现的就是Xcode,然后直接点击安装就行,很方便且智能,如果你的电脑上有旧版本的xcode,它还会提示你删除,反正整个过程我按住下来还是很容易的。
另外,从这篇开始,我使用的教程也做了相应的升级,现在使用的教程为
这个大家去搜一下就可以找到,很方便。
好了,其他的没什么不同,下面开始我们这一篇的学习。
1)Storyboard简介
这次学习的内容是在iOS 5的时候才加入的一个新的东西:Storyboard,简单的翻译成“故事版”(好吧,我觉得这个名字蛮挫的),它简化了一些开发app时的一些步骤,例如自动为我们添加必要的delegate/dataSource,在多个view之间的切换,使用图和线连接各个view,让我们能够清晰的看到各个view之间的前后关系。这样的好处是减轻了我们在管理view之前切换时的工作量,能够让我们把更多的注意力集中在具体的功能实现上,然后是我们对整个的app view之间的关系有一个整体的了解。
Storyboard还是基于xib(Xcode's Interface Builder),但是一个Storyboard中又可以包含多个xib,每个xib都一个它自己的controller绑定。好像,下面先举一个简单的例子,来直观的感受一下什么是Storyboard。
2)Create a Simple Storyboard
创建一个project,左边选择Application,右边选择Single View Application,点击Next
将项目命名为“Simple Storyboard”,然后选中Use Storyboards,单击Next
找个地方保存新建的项目,完成创建
在project navigator中,默认帮我们创建的文件有很多都是和之前一样的,有BIDAppDelegate,BIDViewController,但是我们没有发现xib文件,取而代之的是一个MainStoryboard.storyboard,在这个storyboard中,藏着一个系统默认帮我们创建的xib,选中MainStoryboard.storyboard,在editor area中,会出现一个xib,而整个xib的controller文件正是BIDViewController,至此所有默认创建的文件都已经对上号了。
打开dock,选中View Controller节点并展开,你会发现,在layout area下的一个黑色区域中显示的图标和dock中是一样的,这个黑色区域和上方的view组成了一个场景,叫做scene。(在scene中还有一个Exit,这个就不作介绍了,因为书本里面也是省略的)在view的左边有一个大大的箭头,这个箭头是用来说明该app的起始view是哪个。
在layout area的右下方有一个小图标
,这个是用来切换iphone4和iphone5的(我们的这个例子还是基于iphone4的界面)
好了,简单的介绍就到这里,下面继续我们这个例子,从Object library中拖一个Label放到view的中间,双击Label,输入“Simple”
好了编译运行你的程序,一个最简单的Storyboard app完成了
当我们使用Storyboard开发app的时候,很多事情程序都会帮我们完成,包括如何载入默认的xib。如果你选中project navigator中的项目名称
在editing pane中你可以找到程序默认载入的storyboard,这里例子中默认的storyboard是MainStoryboard.storyboard
3)Storyboard with UITableViewController
在之前几篇的例子中,我们已经很多次的使用到了UITableViewController,对其操作的方式应该已经很熟悉了,一般是tableview中包含很多个cell,每个cell有一个identifier,创建cell的时候用到的方法是cellForRowAtIndexPath。在storyboard中,还是会用到这些,但是会相对简单一些,下面我们接着上面的例子做下去。
选中Project navigator中的Simple Storyboard文件夹,单击鼠标右键,选择“New File...”,在弹出的窗口中,左边选择Cocoa Touch,右边选择Objective-C class,点击Next按钮,在下一个窗口中将class命名为BIDTaskListController,Subclass of命名为UITableViewController,点击Next按钮,完成创建。
接着选中MainStoryboard.storyboard,从Object library中拖一个Table View Controller
到layout area
在dock中,选中刚才拖入的Table View Controller(这样能够抱着我们选中的是layout area中整个的Table View Controller)
打开Identity inspector,将Class设置为BIDTaskListController,当设置完成后,dock中的table view会变成Task List Controller
这样我们新加的TableViewController就和它的类对应起来了,tableview也知道它可以去哪里取得它所需要的数据。在拖入的Table View Controller上,有一个默认的cell(Prototype Cells),我们可以为其添加identifier,在其上面定制自己的cell样式(注意,我们可以添加任意多个Prototype Cells,每个cell用identifier来区分,定制不同的样式,这里的cell只是一个原型,根据原型cell生成正式的cell,之后会有一个这样的例子)。我们选中整个默认的cell,并打开attributes inspector,找到Identifier,输入plainCell
然后从object library中,拖一个Label放到原型cell上,Label如何布局看个人爱好,选中Label,在attributes inspector中找到Tag,将其值设为1,这样在code中就可以通过Tag的值找到Label了。
接着,我们选中整个的cell,你也可以到dock中去选,这样比较准确,然后按Command + D,或者从菜单栏中选择Edit>Duplicate,复制一个cell,这样在table view中就有2个prototype cell了
(这里有一个非常有用的小技巧,当你想直接在view中选择自己想要的元素时,但是又碍于一个view上叠加的元素太多很难直接选中,那么在这时,你同时按住键盘上的shift和control键,然后在你想选择的元素上点击鼠标,会弹出一个窗口,上面罗列了鼠标点击的位置下所有存在的元素,然后你再去进行选择会变的异常的简单。
例如我在cell中的Label上点鼠标
鼠标点击的位置一共有4个层叠元素如果我们在cell上点击鼠标
Label不见了,只有三个元素。
这么样,用这样的方法去选择元素很简单吧,还是蛮佩服苹果在细节方面的考虑和设计的。)
选中新加的cell,在attributes inspector中将Identifier赋值为attentionCell
选中Label,在attributes inspector中将其颜色设置为红色
好了,对于这个table view的操作全部完成,在开始具体的编写代码之前,还有一件事情,将layout area中的那个大大的箭头移到这个table view上,这样程序在载入的时候默认的会显示这个view
保存一下MainStoryboard.storyboard,下面开始具体的编码。
打开BIDTaskListController.m文件,你会发现很多常用的方法已经存在:
viewDidLoad
didReceiveMemoryWarning
numberOfSectionsInTableView
numberOfRowsInSection
cellForRowAtIndexPath
didSelectRowAtIndexPath
我们只要直接往这些方法中填代码就可以了,添加如下代码
#import "BIDTaskListController.h"
@interface BIDTaskListController ()
@property (strong, nonatomic) NSArray *tasks;
@end
@implementation BIDTaskListController
......
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.tasks = @[@"Walk the dog",
@"URGENT: Buy milk",
@"Clean hidden lair",
@"Invent miniature dolphins",
@"Find new henchmen",
@"Get revenge on do-gooder heroes",
@"URGENT: Fold laundry",
@"Hold entire world hostage",
@"Manicure"];
}
......
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete method implementation.
// Return the number of rows in the section.
return [self.tasks count];
}
首先和之前一样,定义一个NSArray类型的tasks,用于保存table view中的行,然后在viewDidLoad中对tasks进行赋值(这里的语法和之前我看到的赋值方法有点不同,越到后面,语句写的越是精炼啊),在numberOfSectionsInTableView中,返回1,表示只有一个section,在numberOfRowsInSection中返回section中row的数量。这些都很简单,接着添加代码
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *identifier = nil;
NSString *task = [self.tasks objectAtIndex:indexPath.row];
NSRange urgentRange = [task rangeOfString:@"URGENT"];
if (urgentRange.location == NSNotFound) {
identifier = @"plainCell";
} else {
identifier = @"attentionCell";
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
// Configure the cell...
UILabel *cellLabel = (UILabel *)[cell viewWithTag:1];
NSMutableAttributedString *richTask = [[NSMutableAttributedString alloc] initWithString:task];
NSDictionary *urgentAttributes = @{NSFontAttributeName : [UIFont fontWithName:@"Courier" size:24],
NSStrokeWidthAttributeName : @3.0};
[richTask setAttributes:urgentAttributes range:urgentRange];
cellLabel.attributedText = richTask;
return cell;
}
代码一开始定义了一个identifier,然后根据indexPath获得tasks中的task,NSRange可以认为是一个范围或者一种排列,它根据这个范围或者排列到另一个对象去寻找,如果找到了就返回开始的位置,如果没有找到就返回NSNotFound。NSRange的对象urgentRange定义了一个字段“URGENT”,它在task中进行匹配,如果存在,那么这个cell就把plainCell赋给identifier,如果不存在则将attentionCell赋给identifier。然后根据identifier从tableView的方法dequeueReusableCellWithIdentifier中得到相应的cell。
之后的一段是对cell上的label进行操作,还记得刚才我们在attributes inspector中将Label的Tag设置为1了吗?这里就用到了,使用viewWithTag的方法在cell中找到Label,然后对Label进行赋值,之后的一些操作是对特定的字符串“URGENT”进行操作,更改它的字体属性。这些就一笔带过吧,毕竟我们的注意力不是集中在这个上面,对Label操作完后,将其赋给cellLabel,最后返回cell。
好了,编译运行(编译时会有warning产生,这个不用去理会,编译还是会成功,这些warning是告诉你在Storyboard中有xib是没有被使用的,我们的箭头没有指向它且和当前的view又没有联系,所以不会对其进行操作,有warning是正常的),效果如下
如果task中包含字符串“URGENT”那么将会使用identifier为attentionCell的cell,否则就使用identifier为plainCell的cell。
4)Static Cells
在大部分的情况下,table view中的cell都是需要动态生成了,app运行时,根据source的不同生成不同数量或者样式的cell,但是在某些情况下,我们可以事先知道将要生成的cell是什么样子的,且它是固定不变的,我们把这样的cell称之为Static Cells,与其对应的则是dynamic cell。在这里我们举一个简单的例子来说明这种情况。
我们不用创建一个新的project,直接在上面的程序中接着添加代码。选中Project navigator中的Simple Storyboard文件夹,单击鼠标右键,选择“New File...”,在弹出的窗口中,左边选择Cocoa Touch,右边选择Objective-C class,点击Next按钮,在下一个窗口中将class命名为BIDStaticCellsController,Subclass of命名为UITableViewController,点击Next按钮,完成创建。
选中MainStoryboard.storyboard,再从Object library中拖一个Table View Controller到layout area,就放在原有2个view的右边,接着将箭头指向这个新添加的view
图中最右边的是新添加的view,这些view看上去比较小,是因为我了layout area右下角的
,这样可以方便观察每一个view(当然在缩小的状态下,是没有办法对view进行操作的,只能移动其位置,要操作view,必须将view放大回正常的大小)选中刚才添加的controller中的table view,打开attributes inspector,找到Content,在其下拉框中选择“Static Cells”,找到Style,在其下拉框中选择“Grouped”
table view的样式也随之发生了变化,出现了3行row,section的样式变成了一个圆角矩形
选中section,在其attributes inspector设置如下,Rows改为2,Header中填写“Silliest Clock Ever”
改完后的section
下面对2个cell进行设置,选中第一个cell,在attributes inspector中将其Style设置为“Left Detail”
然后双击Title,改成“The Date”,重复上面的步骤,将第二个cell的Title改成“The Time”,改完后的效果
之后,我们将创建两个outlet对象,分别指向2个Detail,这样在app运行后,就可以改变它们的值了。
现在先关联这个table view controller和它的类,在dock中选中Table View Controller,然后打开identity inspector,在Class中输入“BDIStaticCellsController”,dock中的名字也随之发生改变
还是在dock中选中controller的状态下,将Editor的模式设置成Assistant editor
,这样BIDStaticCellsController.h文件会打开(如果打开的不是这个文件,那么就手动打开吧),选中第一个cell中的Detail,然后control-drag到BIDStaticCellsController.h中并释放,会弹出个窗口,将Name命名为“dateLabel”
对第二个cell中的Detail进行相同的操作,将Name命名为“timeLabel”,添加完成后的BIDStaticCellsController.h
#import <UIKit/UIKit.h>
@interface BIDStaticCellsController : UITableViewController
@property (weak, nonatomic) IBOutlet UILabel *dateLabel;
@property (weak, nonatomic) IBOutlet UILabel *timeLabel;
@end
下面开始编写代码,打开BIDStaticCellsController.m,先将下面三个方法删除
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete method implementation.
// Return the number of rows in the section.
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
return cell;
}
因为我们使用的是static cell,因此table view中section的数量,section中cell的数量都是固定不变的,我们也不需要从新创建cell,cell一共才2个,会一直显示在屏幕上。
接着添加下面的代码
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
NSDate *now = [NSDate date];
self.dateLabel.text = [NSDateFormatter
localizedStringFromDate:now
dateStyle:NSDateFormatterLongStyle
timeStyle:NSDateFormatterNoStyle];
self.timeLabel.text = [NSDateFormatter
localizedStringFromDate:now
dateStyle:NSDateFormatterNoStyle
timeStyle:NSDateFormatterLongStyle];
}
在viewDidLoad中,分别对dateLabel和timeLabel进行了设置,至于NSDate和NSDateFormatter的说明大家就去google一下吧,这里不做详细解释了。
编译运行,效果如下
下:
这篇我们完成Storyboards的最后一个例子,之前的例子中没有view之间的切换,这篇加上这个功能,使Storyboards的功能完整呈现。在Storyboards中负责view切换的东西叫做“segue”,只需对它进行简单的设置即可,一切都是傻瓜式的,无需繁琐的代码。好了,开始我们的例子吧。
1)Create a Simple Storyboard
创建一个project,左边选择Application,右边选择Empty Application template(我们这里不使用Single View Application,而是创建了一个Empty Application,之后我们会自己手动添加storyboard,这样你就可以更好的了解storyboard使用方法了),点击Next
将项目命名为Seg Nav,点击Next,完成项目创建
由于我们要创建的项目是基于storyboard,因此需要对BIDAppDelegate.m中didFinishLaunchingWithOptions方法进行如下修改
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
删除所有行,只保留最后的return YES。
2)添加storyboard
鼠标右击project navigator中的Seg Nav文件夹,然后选择New File...,在弹出的对话框中,左边选择User Interface,右边选择Storyboard,点击Next
之后的Device Family选择iPhone,点击Next,将storyboard命名为MainStoryboard.storyboard,完成创建
之后一步我们所要做的是将MainStoryboard.storyboard设置为程序启动是默认载入的对象,选中project navigator中最顶端的项目名称
,这样summary tab就会出现,找到里面的Main Storyboard选项,将其值设置为我们刚刚添加的MainStoryboard,这样在陈旭启动后会默认的载入这个storyboard。3)设置MainStoryboard.storyboard
再次选中MainStoryboard.storyboard,这是它里面什么都没有,layout area中也是空空如也,在object library中找到Navigation Controller
,然后将其拖入layout area中,这样在layout area中瞬间就多了2个controller
为什么会是2个controller而不是一个呢?这是因为UINavigationController只包含一个navigation bar,没有其他的东西,因此当我们拖一个Navigation Controller进入layout area后,系统自动为这个Navigation Controller关联了一个Table View Controller,这样就完整了。上图中,2个Controller的中间有一个箭头相连,它表示右边的Table View Controller是左边Navigation Controller的rootViewController(记住箭头中间的圆圈,之后关联其他Controller的时候,其他箭头中间的形状会有所不同)在dock中选中Table View Controller的Table View
打开attrbutes inspector,将Content设置为“Static Cells”,layout area中的table view上出现了3个cell,我们只需要2个就够了,删除其中的一个
在dock中依次选中2个Table View Cell,然后在attributes inspector中将他们的Style改成“Basic”
这样在cell上会出现Title
将上面的一个Title改成“Single view”,下面的一个Title改成“Sub-menu”(直接双击Title进行修改)
然后我们对Table View Controller上的Navigator bar进行操作,在dock中选中Navigation item
将attributes inspector中Title设值为“Segue Navigator”,将Back Button设值为“Seg Nav”
注意,这里的Back Button并不是显示在当前的navigator bar上的,而是显示在下一个sub view controller的navigator bar上的返回按钮的文字,用于表面将返回到哪个父contoller编译运行一下程序
我们可以看到MainStoryboard.storyboard作为默认的view被载入到程序中,界面上显示了我们修改后的navigator bar,还有table view中的2个cell。这样子是不是很简单?到目前为止,对Storyboard的操作完全是基于Interface Builder的,我们没有写过任何代码。
4)创建第一个Segue例子
在project navigator中选中MainStoryboard.storyboard,然后从Object library中找到View Controller
,将其拖入到layou area中,放置在现有controller的右边
然后在Object library中找到Label,拖入到刚才添加的View Controller中,将Label的文字改成“Single view”,从添加的文字可以知道,这个view是通过点击table view中的第一个cell打开的
下面就是通过Segue将table view中的第一个cell和Single view关联起来,首先选中table view中的第一个cell,然后control-drag到新添加的view,然后释放鼠标,这时会有一个弹出框弹出,
这个弹出框有2部分组成,Selection Segue和Accessory Action,这2部分的选项是相同的。
Selection Segue的意思是当用户点击table view cell的任何部分,都会产生反应。
Accessory Action的意思是只有当用户点击table view cell右边的圆圈箭头按钮时,才会产生的反应。在这里,我们选择Selection Segue的push选项(大家可以去一个一个选择其他的选项,看看有什么不同),选中后,在view的上方会自动出现一个navigator bar的占位栏,而在table view cell的右边会出现一个大于号箭头,它们2个view直接会有一个箭头相连
ok,现在编译运行一下,看看效果,点击table view中的第一个cell,切换到下面的view,可以看到,view的最上方是navigator bar,bar的左边是一个退回的按钮,按钮中显示的文字是“Seg Nav”,还记得刚才我们在设置Navigation Item时,在其attributes inspector中的Back Button项中留下的文字吗?就是显示在这里的。
点击Seg Nav按钮可以回到上级view。这个操作过程是不是很简单?回忆之前几篇的例子,我们这里没有写过一行代码,而得到的效果是一样的,Storyboard在这方面还是很强大的,它让程序员的全部注意力都集中在了具体的view的开发上,view之间的切换它都帮我们搞定了。
4)创建第二个Segue例子
下面我们为table view中的第二个cell创建一个controller,然后通过segue将他们连接起来。
第二个cell将连接到我们上一篇的例子Simple Storyboard中BIDTaskListController,因此我们在这里可以稍微偷懒一下,直接将他们拖入到现在的项目中
接着我们还需要添加一个文件,选中Project navigator中的Seg Nav文件夹,单击鼠标右键,选择“New File...”,在弹出的窗口中,左边选择Cocoa Touch,右边选择Objective-C class,点击Next按钮,在下一个窗口中将class命名为BIDTaskDetailController,Subclass of命名为UIViewController,点击Next按钮,完成创建。
接着选中MainStoryboard.storyboard,从Object library中拖一个Table View Controller到layout area(你可以适当调整view的位置,使其布局美观合理)
选中新添加的Table View Controller,打开identity inspector,将Class设置为BIDTaskListController
猜到我们接下来要做什么了吗?由于BIDTaskListController是从之前的Simple Storyboard复制过来的,因此我们需要和之前一样,在新添加的Table View Controller中放2个cell,设置每个cell的identifier,为每个cell中添加1个Label,然后分别为他们设置tag值,并将第二个Label的颜色设置为红色。
首先在dock中选中第一个cell(现在应该只有唯一一个cell存在),然后打开attributes inspector,将其identifier赋值为“plainCell”,往第一个cell中拖一个Label,左右拉伸Label直至辅助线的位置,选中Label,打开attributes inspector,找到Tag,设值为1。
再选中第一个cell,然后Command+D,复制一个cell,新的cell出现在其下方,选中新的cell,在attributes inspector中将其identifier赋值为“attentionCell”,再选中Label,在attributes inspector将Label的颜色改成红色,设置Tag值仍为1。
在开始连接segue之前,我们还需要添加另外一个view controller,用于显示并修改TaskList中每一项的内容,并和BIDTaskDetailController关联,从Object library中拖一个View Controller到layout area,放在Task List Controller的右边,然后在identity inspector中将其class设置为BIDTaskDetailController
当我们在Task List Controller中选中一个cell后,view会切换到Task Detail Controller,并在里面显示cell的文字,我们在Task Detail Controller中可以对cell的文字进行修改保存,然后再返回Task List Controller。因此我们首先需要在Task Detail Controller中添加一个UITextView,UITextView应该是我们第一次接触到,它是一个多行的文本视图,和C#中的TextBox类似。
在Object library中找到Text View
拖入到Task Detail Controller中,这时Text View会自动填充满整个view的空间
我们并不需要它占满整个空间,因此我们去调成它的高度,使其只占满view的上半部分,因为下半部分会显示虚拟键盘,调整Text View的高度到200(移动上图中底下中间的那个小白方框进行调整,你也可以在Size inspector中进行调整,看个人喜欢了),宽度还是占满整个view的
好了,终于开始代码的部分了,在dock中选中Task Detail Controller
然后点击Assistant editor
,这时应该会打开对应的BIDTaskDetailController.h选中Text View,然后control-drag到BIDTaskDetailController.h,在填出框中,name命名为textView,点击Connect。
好了,下面可以开始连接Segue。选中Table View Controller中的第二个cell,然后control-drag到Task List Controller,在填出框中选择Selection Segue的push选项。然后选择Task List Controller中的第一个cell,control-drag到Task Detail Controller,在填出框中选择Selection Segue的push选项,选择Task List Controller中的第二个cell,control-drag到Task Detail Controller,在填出框中选择Selection Segue的push选项,这样就有2个箭头同时从Task List Controller指向Task Detail Controller,但是他们的意义是不一样的。
整个的MainStoryboard.storyboard的结构如下图所示
编译运行一下程序,所以controller之间都应该可以顺利切换了。
但是我在运行的时候却发现了一个问题,当我点击Sub-menu准备切换到Task List Controller时,发现Task List Controller竟然是空白的一片,而起xcode也报错说:Unknown class BIDTaskListController in Interface Builder file
我打开BIDTaskListController.h,发现UITableViewController的文字颜色是黑色,这就说明xcode没有认出整个类,那就说明肯定是编译器出来问题,没有设置正确
网上查了一圈后发现这种现象是xcode的一个bug,因为BIDTaskListController不是我们从xcode创建的,而是从另外一个项目中拖进来的,因此xcode没有把它包含进编译范畴,因此也就没有去识别里面所包含的类。解决整个问题的方法也很简单,我们选中Project navigator中的Seg Nav项目,然后打开Build Phases,展开Compile Sources,点击+号,把BIDTaskListController.m添加进去就可以了。
在此编译运行,你会发现controller都正确显示了,xcode中的错误也消失了。
下面我们实现一些方法,使每个controller能够正确的工作,打开BIDTaskListController.m,添加如下代码
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ UIViewController *destination = segue.destinationViewController; if ([destination respondsToSelector:@selector(setDelegate:)]) { [destination setValue:self forKey:@"delegate"]; } if ([destination respondsToSelector:@selector(setSelection:)]) { // prepare selection info NSIndexPath *indexPath = [self.tableView indexPathForCell:sender]; id object = self.tasks[indexPath.row]; NSDictionary *selection = @{@"indexPath" : indexPath, @"object" : object}; [destination setValue:selection forKey:@"selection"]; } }
prepareForSegue:sender方法,当tasklist中的任何cell被点击,并准备开始进行view直接的切换时,这个方法会被触发,这样我们可以利用这个方法来传输一些信息给下一个view使用。
UIViewController *destination = segue.destinationViewController; // 通过参数segue,我们可以知道接下来即将显示的controller是哪个,segue还有另外一个参数segue.sourceViewController,这个是表示即将被移除的controller是哪个
if ([destination respondsToSelector:@selector(setDelegate:)]) { // respondsToSelector用来判断是否实现了某些方法,这里是判断在目标controller中是否实现了setDelegate方法
[destination setValue:self forKey:@"delegate"]; } // 这里是KVC(KEY-VALUE CODING)的一个用法,将目标对象中key为delegate的对象赋值为self,当然到目前为止,在BIDTaskDetailController中什么方法都没有实现,是空的
if ([destination respondsToSelector:@selector(setSelection:)]) { //用来判断在目标对象中是否存在setSelection方法
// prepare selection info
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender]; // 这里的sender指向的是用户点击的那个cell,通过它来获得cell的indexPath
id self.tasks[indexPath.row]; // 获取NSArray中的对象
NSDictionary@{@"indexPath" : indexPath, @"object" : object}; // 创建一个NSDictionary对象,将indexPath和object对象,然后将他们通过KVC的方法传到目标controller中,这里传递indexPath的作用是如果在BIDTaskDetailController中把object的内容给改了,那么传回来的时候,我们就知道改的是哪个cell了,否则们会云里雾里,不知道哪个cell的内容需要更新
[destination setValue:selection forKey:@"selection"]; } // 这里又用到KVC,将目标对象selection的值设置为selection
下面开始修改BIDTaskDetailController.h,打开它,添加如下代码
#import <UIKit/UIKit.h>
@interface BIDTaskDetailController : UIViewController
@property (weak, nonatomic) IBOutlet UITextView *textView;
@property (copy, nonatomic) NSDictionary *selection;
@property (weak, nonatomic) id delegate;
@end
刚才使用KVC赋值的2个对象在这里定义好了,注意,selection用的是copy
打开BIDTaskDetailController.m,添加如下代码
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.textView.text = self.selection[@"object"];
[self.textView becomeFirstResponder];
}
在viewDidLoad方法中,首先对textView进行赋值,selection在之前的controller中使用KVC进行过赋值,在这里直接获取就可以了,然后调用becomeFirstResponder,呼出虚拟键盘。
ok,再编译运行一下你的程序,看看效果,随便在BIDTaskListController中选择一个cell,然后立刻会跳转到BIDTaskDetailController,而且textView中会显示cell的内容,并且出现虚拟键盘
但是如果现在修改了textView的内容,然后返回,textView中的内容是没有办法保存的,我们还没有实现这个功能,那是不是我们也可以用刚才同样的方法prepareForSegue来传递值呢?很不幸,不可以,因为prepareForSegue只有当将一个controller放到堆栈上面的时候可以使用,如果将一个controller从堆栈上面移除,是无法使用的,这个具有单向性,好吧,那我们只有另寻他法了,在BIDTaskDetailController.m中添加如下代码
- (void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; if ([self.delegate respondsToSelector:@selector(setEditedSelection:)]) { // finish editing [self.textView endEditing:YES];
// prepare selection info
NSIndexPath *indexPath = self.selection[@"indexPath"];
id object = self.textView.text;
NSDictionary *editedSelection = @{@"indexPath" : indexPath, @"object" : object};
[self.delegate setValue:editedSelection forKey:@"editedSelection"];
}
}
现在再看这个方法,应该是很熟悉了吧,里面实现的东西还是一样的,只是没有放在prepareForSegue中而已,setEditedSelection等一会会在BIDTaskListController中实现,需要解释的貌似就一个[self.textView endEditing:YES]:停止一切对textView的编辑动作,等对textView的操作都结束后,那么就可以获取它的值,然后返回了。其他的代码都应该可以理解,最后也用到了KVC方法。
最后还是需要对BIDTaskListController进行一些修改,打开BIDTaskListController.m,做如下修改
@interface BIDTaskListController ()
@property (strong, nonatomic) NSArray *tasks;
@property (strong, nonatomic) NSMutableArray *tasks;
@property (copy, nonatomic) NSDictionary *editedSelection;
@end
我们将tasks对象替换成可修改的Array,然后再声明一个NSDictionary对象editedSelection
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.tasks = @[@"Walk the dog",
@"URGENT: Buy milk",
@"Clean hidden lair",
@"Invent miniature dolphins",
@"Find new henchmen",
@"Get revenge on do-gooder heroes",
@"URGENT: Fold laundry",
@"Hold entire world hostage",
@"Manicure"];
self.tasks = [@[@"Walk the dog",
@"URGENT: Buy milk",
@"Clean hidden lair",
@"Invent miniature dolphins",
@"Find new henchmen",
@"Get revenge on do-gooder heroes",
@"URGENT: Fold laundry",
@"Hold entire world hostage",
@"Manicure"] mutableCopy];
}
将数组copy到tasks对象中
- (void)setEditedSelection:(NSDictionary *)dict{ if (![dict isEqual:self.editedSelection]) { _editedSelection = dict; NSIndexPath *indexPath = dict[@"indexPath"]; id newValue = dict[@"object"]; [self.tasks replaceObjectAtIndex:indexPath.row withObject:newValue];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
if (![dict isEqual:self.editedSelection]) { // 首先判断dictionary对象是否发生了变化,如果是,继续执行代码
_editedSelection // _editedSelection也是一个objective-c的语法现象,它是隐式的被创建的,在声明
@property (strong, nonatomic) NSMutableArray *tasks;的时候,系统自动声明的了一个对象_editedSelection,你可以发现,这里并没有之前的synthesize方法,这里系统已经帮我们做完了(貌似以后的编程越来越方便了)
NSIndexPath@"indexPath"]; // 获取indexPath对象
id@"object"]; // 获取object对象
[self.tasks replaceObjectAtIndex:indexPath.row withObject:newValue]; // 根据indexPath替换tasks中的对象
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } // 重新载入更新过的cell
好了,至此,左右的代码都写完了,我们的这个例子也完成了,编译运行,试着修改一些cell的内容,然后返回,看看是不是变了
好了,所有关于Storyboard的内容都讲完了,是不是觉得还是蛮简单的,是不是觉得以后再遇到Navigation的项目,都会用Storyboard,而不会自己去写繁琐的code进行view直接的切换呢,好吧,如果没有十分的必要,书本上也建议使用Storyboard的,这样我们可以将更多的精力放在功能的实现上,而无需去处理繁琐的切换效果。