Storyboard是苹果官方主推的一个代替xib的策略。有必要详细学习下它的使用方法。
先来看一下思维导图
![](https://github.com/zhang759740844/MyImgs/blob/master/MyBlog/storyboard_28.png?raw=true)
storyboard基础
storyboard优势
storyboard能代替nib自然有其优势,一般来说storyboard具有以下几种优点:
- storyboard能将nib汇总统一管理
- storyboard可以描述各种场景之间的过渡,这种过渡被称作
segue
,storyboard 把 view controller叫做:scene
,可以通过拖拽实现过度,减少代码 - 支持tableview的prototype cell,可以在storyboard中编辑cell,减少代码量
storyboard的基本使用
启动storyboard
加载storyboard肯定是需要一个主入口的,这个主入口在info.plist
中:
![](https://i-blog.csdnimg.cn/blog_migrate/518ea8c9d6022822e20a240686f1cb2d.png)
- 一般情况下, 程序都是写在Xcode默认的Main.storyboard里面. 但是如有特殊需求, 可自定义StoryBoard:
![](https://i-blog.csdnimg.cn/blog_migrate/0a6d5734427361a1eea8a794fd141e16.webp?x-image-process=image/format,png)
初始化ViewController
确定了哪个storyboard是主入口,那显然也要确定哪个ViewController是storyboard的主入口。需要选中相应ViewController,勾选Is Initial View Controller
![](https://i-blog.csdnimg.cn/blog_migrate/e2b7e79c75298857dc05eb333a7ec595.png)
此时,对应ViewController前出现一个箭头
![](https://i-blog.csdnimg.cn/blog_migrate/f923fa44e12d575f42e98b6a137f2cf0.png)
创建relationship segue
对于三大container view controller,即Tab Bar Controller,Navigation Controller,Split View Controller ,可以通过拖拽创建设置relationship segue。
如下图的 popup menu 是从tab bar controller 连到navigation controller,松手后的弹出:
![](https://i-blog.csdnimg.cn/blog_migrate/690fbeddb6061b547f2cc8f6fc131a77.png)
连接后的图标如下图,表示relationship segue
![](https://i-blog.csdnimg.cn/blog_migrate/e8c98e614fcda391cc83f5deab96253b.png)
命名tabbar controller的tabbar
并非在tabbar controller里,而是在与其相连、对应的controller里改动,如图:
![](https://i-blog.csdnimg.cn/blog_migrate/28aeb1634b91a3a53dbc7f11a2371a0e.png)
navigation bar 的 title 也是同理。但是,强烈不建议在storyboard里设置navigationbar,因为storyboard是为了简化操作的,但是设置navigationbar太麻烦了,还不如代码方便实用。
设置ViewController对应的类
选中相应ViewController,然后在 Custom Class 内写上相应类名即可。注意,要选中 ViewController 而不是其中的 View,要点击图中的黄色圆形按钮。
![](https://i-blog.csdnimg.cn/blog_migrate/6c8ea9847fb24b0c6bc82a2a9c278621.png)
获取视图控制器
就是通过UITableViewController
和UINavigationController
中的viewControllers
获取:
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
UINavigationController *navigationController = [tabBarController viewControllers][0];
PlayersViewController *playersViewController = [navigationController viewControllers][0];
Prototype cells
选中tableview,设置tableview的 cotent 为 Dynamic Propotype
![](https://i-blog.csdnimg.cn/blog_migrate/f55a1c1526d50963f0c831b699b0f338.png)
一般在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,如图:
![](https://i-blog.csdnimg.cn/blog_migrate/728ce096850834ab927e2d1c044e0f72.png)
设置不同的 identifier 来标识不同的cell。
除了在viewcontroller里直接创建cell,不需要另一个cell的xib的区别外,其他方面和xib无异。也可以选中cell,在右边栏指定对应的Controller的custom class用来控制cell。同样的,也不能直接把cell中的view连线到cell所属的viewController中。
Static cells
使用静态的cell,适用在仅有几个确定cell的tableview中,不能重用,设置了几个cell,就显示几个cell。static cells的设置如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/f68dd157eb6ec73aceab779732be0b04.png)
因为prototype cells究竟怎么显示可以在代码中设置,所以只需要设置有几个可重用的cell就行了,而static cells因为不可重用,那么这里的设置选项就变成了 Sections 设置多少段。
那么怎么设置每个section有多少个cell呢?选中如下图所示的只有static cells才有的蓝色立方体:
![](https://i-blog.csdnimg.cn/blog_migrate/9e2261d51439335c34b4ddbd43c9aab3.png)
此时右边栏出现如图所示 Table View Section
![](https://github.com/zhang759740844/MyImgs/blob/master/MyBlog/storyboard_12.png?raw=true)
可以设置数量,表头表尾的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中连线设置就可以了。
使用segue
简介
什么是Segue
Storyboard上每一根用来界面跳转的线,都是一个UIStoryboardSegue对象(简称Segue)
Segue的属性
每一个Segue对象,都有3个属性
// 唯一标识
@property (nonatomic, readonly) NSString *identifier;
// 来源控制器
@property (nonatomic, readonly) id sourceViewController;
// 目标控制器
@property (nonatomic, readonly) id destinationViewController;
Segue的类型
根据Segue的执行(跳转)时刻,Segue可以分为2大类型:
- 自动型(Action segue):点击某个控件后(比如按钮),自动执行Segue,自动完成界面跳转。
- 手动型(Manual segue):需要通过写代码手动执行Segue,才能完成界面跳转。
segue执行过程
手动调用performSegueWithIdentifier:sender:
方法实现跳转。那么这期间发生了什么呢?大致分为三个部分。
- 根据
identifier
去storyboard中找到对应的线,新建UIStoryboardSegue对象
- (instancetype)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination; // Designated initializer
其实就是执行了UIStoryboardSegue中initWithIdentifier:source:destination:
方法,并且identifier
就是在Storyboard中Segue属性设置的标识. 来源就是连线的头部. 目标就是连线尾
- 调用sourceViewController的下面方法,做一些跳转前的准备工作并且传入创建好的Segue对象
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;
所谓跳转前的准备,因为可以拿到Segue(来源控制器,目标控制器),所以就可以在这里给下一个控制器传递数据。这个方法是系统默认调用,所以只需要实现即可。另外,只能由来源控制器调用,来拿到目标控制器。
- 调用Segue对象的
perform
方法开始执行界面跳转操作。
页面跳转
segue可以实现页面间跳转,除了上面的 relationship segue 还有 Action segue 和 Manual segue,分别对应button跳转和viewController跳转。
跳进
使用storyboard
Action segue 比较简单,就是将button连到要展示的viewController上,当点击时,就会触发。
Manual segue 相对比较麻烦,但是比较灵活。它设置了两个viewController的跳转关系,在你需要的时候出发跳转。
首先,先对两个viewController进行连线:
![](https://i-blog.csdnimg.cn/blog_migrate/b2a522e26021f64a4582270b61c2df3d.png)
之后点击连线后两个viewController之间产生的箭头,在右边栏可以看到如下:
![](https://i-blog.csdnimg.cn/blog_migrate/ac6c9316248e1e015b2a81a1bf3b9521.png)
其中参数 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:
![](https://i-blog.csdnimg.cn/blog_migrate/daf7369c45cbfee4e880b85a87852839.png)
那个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 跳转至任意连线的位置,也可以使用代码跳转。
exit segue
跳出和跳进的方法类似,略有区别,比如要从界面2跳转回界面1:
先打开需要返回到的界面ViewController1.m,加上下面方法,返回类型一定是IBAction
,参数类型一定是UIStoryboardSegue
,名称随便(这个方法一定要加,返回时调用的)
//其他界面返回到此界面调用的方法
- (IBAction)ViewController1UnwindSegue:(UIStoryboardSegue *)unwindSegue {
}
右键2界面上方的Exit(下图中画绿圈的)弹出菜单中可以看到刚才在1界面中加的那个方法的名称(下图中红色圈里),然后如下图一样连线,弹出菜单选择manual
,这里连接自己表示要在当前viewcontroller中用代码的方式回退。
![](https://i-blog.csdnimg.cn/blog_migrate/ca51f70a61ce468f2c1b68346528586e.png)
给2视图的unwind segue取一个名字叫from2to1
的identifier如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/1965bb8593338d0e41b8ce747e0e0b06.png)
现在就可以在界面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];
跳转的方式
在进行跳转连线后会出现如下窗口:
![](https://i-blog.csdnimg.cn/blog_migrate/7bfd7bd9d7fced1157a2b067af5df237.png)
共有三种跳转方式,也就是上面右边栏的Kind
属性
push
Push类型必须用在NavigationController中,否则报错。是在navigation View Controller中下一级时使用的那种从右侧划入的方式。
model
最常用的场景,新的场景完全盖住了旧的那个。用户无法再与上一个场景交互,除非他们先关闭这个场景。可以在右边栏的Presentation
选择需要展示的动画效果。
custom
自定义类型,需要继承UIStoryboardSegue类,然后重写Perform方法,然后在Storyboard上将类设置为自定义的类。
![](https://i-blog.csdnimg.cn/blog_migrate/f6c2aa995c005c6bec5e1dcc2859b897.png)
这段代码的作用是创建从中心渐变充满屏幕的动画:
-(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
主要应用在下面这种情况:
![](https://i-blog.csdnimg.cn/blog_migrate/0f07a94bf154125c2708a5be26fd96f0.png)
navigationController要分情况跳转到界面A或者界面B,但是navigationController只能有一个rootViewController
啊。所以,通过一个空的ParentViewController
在viewWillAppear:
方法中加载任意一个ChildViewController
。如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/2a12db25d540307903a7077b788a84c9.png)
注意,上图红框中的分支,其实表现的只是一个页面的两种形态,本质上还是一个页面。所以加载childViewController
的segue都要用不带任何动画的custome
类型。因为如果ChildViewController
有动画,那么就会暴露出ParentViewController
中的空白部分,就表现为两个页面了。
可以看出,这个方法的优点是可以通过parent
,从C1
直接跳到C2
,如果不用这种父子ViewController的方式,那么不可避免的就得先从C1
跳回前一级页面,然后再从前一级页面跳到C2
。不过缺点就是C1
到C2
的跳转没有任何跳转动画。
不过,思考了一段时间后,觉得多分支NavigationController本身并不是一个问题,用父子ViewController的方式虽然能解决,但是把问题复杂化了。比如应用在登录跳转上,我完全可以不用在加载NavigationController后再判断是否要登录,而是把这一过程放到加载NavigationController之前。至于可以直接跳转的这一好处,一般情况下,产品也不会这么设计,而且它的弊端也是很明显的。
具体实现的过程参见基于Storyboard的创建多分支NavigationController的方法。记得如果使用这种方法的话,一定要清除Parent
中上次显示的ChildViewController
,文中在prepareForSegue:sender:
方法中清除,是个很好的时机。
storyboard reference
iOS9中,苹果引入了 storyboard reference 用以减小storyboard的体积,方便管理(并不知道iOS9之前怎么用多个storyboard)。
简化现有storyboard
如下图,是做上面练习时创建的一个storyboard,界面已经有点多了。可以使用storyboard reference简化,将一部分viewcontroller拆分到其他storyboard里。
![](https://i-blog.csdnimg.cn/blog_migrate/ea9769d9f130e0077b482be368e6cd34.png)
做法其实很简单,选中想要拆分的viewcontroller,然后在菜单栏干中“Editor->Refactor to Storyboard”,如下图所示。然后命名新的storyboard即可。
![](https://i-blog.csdnimg.cn/blog_migrate/ee821ea3225c45947cbd71992ebe804c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e3102f1bb9aec71400f0eff2a2b6c639.png)
加载storyboard中一个特定viewController
和拖拽其他控件一样,找到storyboard控件,拖拽到storyboard上:
![](https://i-blog.csdnimg.cn/blog_migrate/f786baaddb4e08018926713a9e9f502b.png)
然后设置storyboard:
![](https://i-blog.csdnimg.cn/blog_migrate/4e6e52a3381c8448c79b3a756904c9d5.png)
这里面storyboard
填的是目标storboard的文件名;Reference ID
是啥?从它的提示也就才出来了,用来确定联结的是那个viewController,填的是目标storyboard中目标viewController的Storyboard ID
,具体在哪设,上面也说过。
这样,一个简洁的storyboard就能创建出来了。
参考链接
iOS-prepareForSegue场景切换,KVC传值;
(4.4.1)使用storyboard实现页面跳转,简单的数据传递;
【iOS界面处理】使用storyboard实现页面跳转,简单的数据传递
iOS9 Day-by-Day :: Day 3 :: Storyboard References;
FROM:
一篇较为详细的 Storyboard使用方法 总结
http://www.jianshu.com/p/bdf89251f083
iOS学习之StoryBoard入门
http://www.jianshu.com/p/496bbe9a88be