Storyboard是苹果官方主推的一个代替xib的策略。有必要详细学习下它的使用方法。
先来看一下思维导图
storyboard能代替nib自然有其优势,一般来说storyboard具有以下几种优点:
segue
,storyboard 把 view controller叫做:scene
,可以通过拖拽实现过度,减少代码 加载storyboard肯定是需要一个主入口的,这个主入口在info.plist
中:
确定了哪个storyboard是主入口,那显然也要确定哪个ViewController是storyboard的主入口。需要选中相应ViewController,勾选Is Initial View Controller
此时,对应ViewController前出现一个箭头
对于三大container view controller,即Tab Bar Controller,Navigation Controller,Split View Controller ,可以通过拖拽创建设置relationship segue。
如下图的 popup menu 是从tab bar controller 连到navigation controller,松手后的弹出:
连接后的图标如下图,表示relationship segue
并非在tabbar controller里,而是在与其相连、对应的controller里改动,如图:
navigation bar 的 title 也是同理。但是,强烈不建议在storyboard里设置navigationbar,因为storyboard是为了简化操作的,但是设置navigationbar太麻烦了,还不如代码方便实用。
选中相应ViewController,然后在 Custom Class 内写上相应类名即可。注意,要选中 ViewController 而不是其中的 View,要点击图中的黄色圆形按钮。
就是通过UITableViewController
和UINavigationController
中的viewControllers
获取:
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
UINavigationController *navigationController = [tabBarController viewControllers][0];
PlayersViewController *playersViewController = [navigationController viewControllers][0];
选中tableview,设置tableview的 cotent 为 Dynamic Propotype
一般在tableView: cellForRowAtIndexPath:
方法中都像下面:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
// Configure the cell...
return cell;
}
但是,由于在storyboard中已经创建了cell,那么就可以直接使用了:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PlayerCell"];
// Configure the cell...
return cell;
}
当然,一个tableview里肯定不应该只有一个,可以把上面的 Content 下面的 Prototype Cells 增加cell,然后选择任意cell,如图:
设置不同的 identifier 来标识不同的cell。
除了在viewcontroller里直接创建cell,不需要另一个cell的xib的区别外,其他方面和xib无异。也可以选中cell,在右边栏指定对应的Controller的custom class用来控制cell。同样的,也不能直接把cell中的view连线到cell所属的viewController中。
使用静态的cell,适用在仅有几个确定cell的tableview中,不能重用,设置了几个cell,就显示几个cell。static cells的设置如下图:
因为prototype cells究竟怎么显示可以在代码中设置,所以只需要设置有几个可重用的cell就行了,而static cells因为不可重用,那么这里的设置选项就变成了 Sections 设置多少段。
那么怎么设置每个section有多少个cell呢?选中如下图所示的只有static cells才有的蓝色立方体:
此时右边栏出现如图所示 Table View Section
可以设置数量,表头表尾的title
和 prototype cell 一样,static cell可以指定一个专门的Controller。但是不同的是,static cell 的cell以及cell中的控件都相当于确定的view,因此,static cell可以把cell以及cell中的控件连线到cell所属的viewController中。
也就是说,如果在cell的Controller中设置了一个button的点击事件,然后又在cell所属viewController中又设置了一次该button的点击事件,不会报错,两个点击事件都会触发。
所以,方便起见,static cell 直接在viewController中连线设置就可以了。
Storyboard上每一根用来界面跳转的线,都是一个UIStoryboardSegue对象(简称Segue)
每一个Segue对象,都有3个属性
// 唯一标识
@property (nonatomic, readonly) NSString *identifier;
// 来源控制器
@property (nonatomic, readonly) id sourceViewController;
// 目标控制器
@property (nonatomic, readonly) id destinationViewController;
根据Segue的执行(跳转)时刻,Segue可以分为2大类型:
手动调用performSegueWithIdentifier:sender:
方法实现跳转。那么这期间发生了什么呢?大致分为三个部分。
identifier
去storyboard中找到对应的线,新建UIStoryboardSegue对象- (instancetype)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination; // Designated initializer
其实就是执行了UIStoryboardSegue中initWithIdentifier:source:destination:
方法,并且identifier
就是在Storyboard中Segue属性设置的标识. 来源就是连线的头部. 目标就是连线尾
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;
所谓跳转前的准备,因为可以拿到Segue(来源控制器,目标控制器),所以就可以在这里给下一个控制器传递数据。这个方法是系统默认调用,所以只需要实现即可。另外,只能由来源控制器调用,来拿到目标控制器。
perform
方法开始执行界面跳转操作。segue可以实现页面间跳转,除了上面的 relationship segue 还有 Action segue 和 Manual segue,分别对应button跳转和viewController跳转。
Action segue 比较简单,就是将button连到要展示的viewController上,当点击时,就会触发。
Manual segue 相对比较麻烦,但是比较灵活。它设置了两个viewController的跳转关系,在你需要的时候出发跳转。
首先,先对两个viewController进行连线:
之后点击连线后两个viewController之间产生的箭头,在右边栏可以看到如下:
其中参数 identifier
就是跳转的标识符,根据这个标识符来确定跳转到是那个页面。下面几个参数,下面再说。
接下来就可以调用方法,在合适的时机加载了
//根据 segue Identifier跳转界面
[self performSegueWithIdentifier:@"GotoTwo" sender:self];
其中的identifer
自然不用多说,那么sender
是什么呢?sender
是参数名称,理论上可以指代任何对象,用来区分是哪个控件触发了segue。比如有两个button都跳转到一个页面,那么这时就可以设置sender
区分了。引申开来,在设置button点击事件时的-(IBAction)click:(id)sender;
方法中的sender
和这里的sender
是一个作用。
上面的方法实现效果和平时用的下面两个方法相同:
//以modal 方式跳转
[self presentViewController:ViewController animated:YES completion:nil];
//压进一个viewcontroller
[self.navigationController pushViewController:ViewController animated:YES completion:nil];
不过,既然用了storyboard了,那么实例化viewController时就不能用initWithNibName
了。在storyboard中,要通过storyboard找到viewController的布局。首先要设置viewcontroller的 storyboardID:
那个use storyboardId
的勾不打也行,不知道干什么用的,
现在就可以在代码中找到特定storyboard的viewcontroller了:
- (IBAction)tapButton:(id)sender {
//获取storyboard: 通过bundle根据storyboard的名字来获取我们的storyboard,
UIStoryboard *story = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
//由storyboard根据myView的storyBoardID来获取我们要切换的视图
UIViewController *myView = [story instantiateViewControllerWithIdentifier:@"myView"];
//显示ViewController
[self presentViewController:myView animated:YES completion:nil];
}
能跳进当然也要能跳出,可以使用 exit segue 跳转至任意连线的位置,也可以使用代码跳转。
跳出和跳进的方法类似,略有区别,比如要从界面2跳转回界面1:
先打开需要返回到的界面ViewController1.m,加上下面方法,返回类型一定是IBAction
,参数类型一定是UIStoryboardSegue
,名称随便(这个方法一定要加,返回时调用的)
//其他界面返回到此界面调用的方法
- (IBAction)ViewController1UnwindSegue:(UIStoryboardSegue *)unwindSegue {
}
右键2界面上方的Exit(下图中画绿圈的)弹出菜单中可以看到刚才在1界面中加的那个方法的名称(下图中红色圈里),然后如下图一样连线,弹出菜单选择manual
,这里连接自己表示要在当前viewcontroller中用代码的方式回退。
给2视图的unwind segue取一个名字叫from2to1
的identifier如下图:
现在就可以在界面2中的任意时候调用方法回退了:
- (IBAction)back:(id)sender {
//执行segue跳页的方法
[self performSegueWithIdentifier:@"from2to1" sender:nil];
}
使用的仍是跳进时用的方法,不过第一步的操作已经告诉xcode这是一个回退操作了。可以从上图看到,这个 Unwind Segue 绑定了回退到的界面的一个方法,因此,执行跳转后会执行绑定的方法:
//其他界面返回到此界面调用的方法
- (IBAction)ViewController1UnwindSegue:(UIStoryboardSegue *)unwindSegue {
if ([unwindSegue.identifier isEqualToString:@"from2to1"]) {
_lbShowMessage.text = @"从2退到1";
} else if ([unwindSegue.identifier isEqualToString:@"from3to1"]) {
_lbShowMessage.text = @"从3退到1";
}
}
这里就看出上面为什么说类型一定是UIStoryboardSegue
了,因为可以接收一个该类型的对象,以此判断是从哪个页面的回退。
使用 exit segue 的好处是可以跳转到任意打开过的界面比如从3->1,而不是只能返回上级界面从2->1。
也可以使用代码根据是model类型还是push类型选择:
//弹出一个viewcontroller 相当与返回上一个界面
[self.navigationController popViewControllerAnimated:YES];
// 以 modal跳转的返回方法
[self dismissViewControllerAnimated:YES];
在进行跳转连线后会出现如下窗口:
共有三种跳转方式,也就是上面右边栏的Kind
属性
Push类型必须用在NavigationController中,否则报错。是在navigation View Controller中下一级时使用的那种从右侧划入的方式。
最常用的场景,新的场景完全盖住了旧的那个。用户无法再与上一个场景交互,除非他们先关闭这个场景。可以在右边栏的Presentation
选择需要展示的动画效果。
自定义类型,需要继承UIStoryboardSegue类,然后重写Perform方法,然后在Storyboard上将类设置为自定义的类。
这段代码的作用是创建从中心渐变充满屏幕的动画:
-(void)perform{
UIViewController * svc = self.sourceViewController;
UIViewController * dvc = self.destinationViewController;
[svc.view addSubview:dvc.view];
[dvc.view setFrame:svc.view.frame];
[dvc.view setTransform:CGAffineTransformMakeScale(0.5, 0.5)];
[dvc.view setAlpha:0.0];
[UIView animateWithDuration:1.0
animations:^{
[dvc.view setTransform:CGAffineTransformMakeScale(1.0, 1.0)];
[dvc.view setAlpha:1.0];
}
completion:^(BOOL finished) {
[svc presentViewController:dvc animated:NO completion:nil];
}];
}
其实实质还是presentViewController
,但是不用系统带的animation
,而是先将destinationViewController
的页面用动画加载后,直接present
。
前面说到,- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
方法会在跳转时自动触发。跳转传值就在这个方法内完成。
我们可以对Segue的标识进行判断,一般有以下两种方法:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqual: @"login2index"]) {
// 需要执行的代码
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.destinationViewController isKindOfClass:[IndexTableViewController class]]) {
// 执行代码
}
}
第一种需要在设置标识的值,并且匹配。第二种却是通过目标控制器判断。个人感觉还是第一种靠谱一些。
接下来就可以对destination
进行赋值了:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(@"触发该场景切换的sender对象的类型是:%@",[sender class]);
//方法一,使用KVC给B 也就是目标场景传值
UIViewController *destinationController=[segue destinationViewController];
[destinationController setValue:@"119" forKey:@"number"];
//方法2,使用属性传值,需导入相关的类.h
//BViewController *bController=[segue destinationViewController];
//bController.number=@188;
}
跳转传值不仅可以用prepareForSegue:sender:
实现,也可以通过代理、通知的方式,不过这样挺麻烦的,不推荐。具体参见使用storyboard实现页面跳转,简单的数据传递
主要应用在下面这种情况:
navigationController要分情况跳转到界面A或者界面B,但是navigationController只能有一个rootViewController
啊。所以,通过一个空的ParentViewController
在viewWillAppear:
方法中加载任意一个ChildViewController
。如下图:
注意,上图红框中的分支,其实表现的只是一个页面的两种形态,本质上还是一个页面。所以加载childViewController
的segue都要用不带任何动画的custome
类型。因为如果ChildViewController
有动画,那么就会暴露出ParentViewController
中的空白部分,就表现为两个页面了。
可以看出,这个方法的优点是可以通过parent
,从C1
直接跳到C2
,如果不用这种父子ViewController的方式,那么不可避免的就得先从C1
跳回前一级页面,然后再从前一级页面跳到C2
。不过缺点就是C1
到C2
的跳转没有任何跳转动画。
不过,思考了一段时间后,觉得多分支NavigationController本身并不是一个问题,用父子ViewController的方式虽然能解决,但是把问题复杂化了。比如应用在登录跳转上,我完全可以不用在加载NavigationController后再判断是否要登录,而是把这一过程放到加载NavigationController之前。至于可以直接跳转的这一好处,一般情况下,产品也不会这么设计,而且它的弊端也是很明显的。
具体实现的过程参见基于Storyboard的创建多分支NavigationController的方法。记得如果使用这种方法的话,一定要清除Parent
中上次显示的ChildViewController
,文中在prepareForSegue:sender:
方法中清除,是个很好的时机。
iOS9中,苹果引入了 storyboard reference 用以减小storyboard的体积,方便管理(并不知道iOS9之前怎么用多个storyboard)。
如下图,是做上面练习时创建的一个storyboard,界面已经有点多了。可以使用storyboard reference简化,将一部分viewcontroller拆分到其他storyboard里。
做法其实很简单,选中想要拆分的viewcontroller,然后在菜单栏干中“Editor->Refactor to Storyboard”,如下图所示。然后命名新的storyboard即可。
和拖拽其他控件一样,找到storyboard控件,拖拽到storyboard上:
然后设置storyboard:
这里面storyboard
填的是目标storboard的文件名;Reference ID
是啥?从它的提示也就才出来了,用来确定联结的是那个viewController,填的是目标storyboard中目标viewController的Storyboard ID
,具体在哪设,上面也说过。
这样,一个简洁的storyboard就能创建出来了。
【Storyboard】Storyboard介绍及使用
UIStoryboardSegue讲解;
iOS-prepareForSegue场景切换,KVC传值;
(4.4.1)使用storyboard实现页面跳转,简单的数据传递;
【iOS界面处理】使用storyboard实现页面跳转,简单的数据传递
iOS9 Day-by-Day :: Day 3 :: Storyboard References;
基于Storyboard的创建多分支NavigationController的方法;
iOS 9 Storyboard 教程(一下);
10 Practical Tips for iOS Developers Using Storyboards;
还有一些看完随手就关了,没有记录。
本人的处女篇。iOS菜鸟,水平有限,如有错误,多多指正~