如何较为优雅地使用 Storyboard

在第二份工作前,我是手写UI的忠实拥护者,顶多用xib自定义cell。在交互原型和后台数据都确定的情况下,很难会有很大的改动,而且用数据驱动页面,做一些微调,看着就很优雅。切图、标注、备注和完善的后台文档,做客户端开发可以说是行云流水,而且,自然而然地想做更多的优化。

频繁的、大幅度的需求改动

然而,当上述条件都不存在时,即没有交互原型,只有下三滥的美工出图,图切都不切扔给客户端工程师,更别说标注了,后台数据频繁改动,别说文档了,连数据返回示例都不一定有,状态码除了200,其它全部无从知道含义。

在这种三天两头改设计图甚至换掉整个页面,数据也改来改去,接口要问后台才能确定具体信息的情况下,手写UI是极其不明智的选择。

Masonry本身只是对AutoLayout的封装,提供链式编程,看上去比原来直接使用AutoLayout要直观,但如果纯手写UI的话,仍然会是一坨一坨的,在有切图,标注的情况下,手写UI还是能保持较高的效率。正如上述,无切图标注且频繁改动的情况下,毫不夸张地说,手写UI是自虐。

从 StoryBoard 种实例化控制器

其实通过storyboard的一些设置,几乎可以不用代码实例化控制器再push,但个人认为不够直观,对于一些跳转,还是倾向于用代码从storyboard实例化后再跳转。

代码如下

1
2
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:SBName bundle:nil];
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([self class])];

根据storyboard的名字获取storyboard,事先在storyboard中控制器的设定好storyboard id,这要求在这个storyboard里是唯一的,然后根据这个id从storyboard中初始化这个控制器。

这里面有两个常量,storyboard的文件名和控制器的storyboard id。

storyboard文件名可以用pch文件宏定义或者定义const字符串常量,如集中管理URI那样处理即可。

至于控制器的id,一般storyboard的控制器都会继承自一个自定义的控制器,自定义的控制器又会继承一个基类控制器,在基类控制器加入一个类方法,加入如上代码,并把storyboard中的id设置所继承的控制器类名。

1
2
3
4
5
+ (id)creatViewContollerWithidentifierFromStoryboard:(NSString *)SBName {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:SBName bundle:nil];
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([self class])];
return viewController;
}

这样,在需要从storyboard实例化控制器的地方,用该类方法实例化即可。业务逻辑放在控制器中,而当UI会根据数据复杂变动时,可以让Storyboard中的view继承自定义的view,将一些计算后的UI拼装改动放在自定义view中。

简单来说,Storyboard能高效地拼装UI,充分利用这一特性,可以摆脱需求频繁改动带来的手写UI重新布局耗时烦恼。

可以使用Storyboard Reference了

Storyboard Reference在iOS 9里发行,对于这几代的iOS 开发来说,横跨3 代iOS 系统就满足需求了,根据苹果官网最新统计显示,iOS 8用户占比已经接近1%,加上iOS 11不低的装机率,可以预见的是,很多应用将在近期不再支持iOS 8。

与鸡肋的同在iOS 9发行的UIStackView不同,如果团队开发用 Storyboard 的话,那么肯定会用Storyboard Reference,它能有效分割Storyboard文件,以前的那些八卦图、蜘蛛网将成为历史。

Storyboard Reference的使用非常简单,重构已有项目,只需要选中需要分离的场景,然后选择菜单栏的Editor中的Refactor to Storyboard根据提示操作即可。

也可以手动添加Storyboard Reference,添加后完成拖线,并把Storyboard Reference的Storyboard选项选好,到该Storyboard 中把起始场景的 Is Initial View Controller 勾上。如果是tabbar的Storyboard Reference,这个控制器里加上tabbar item。重构已经自动添加上了。

如果还需要自定义tabbaritem,原先从tabbar获取item再改样式已经不可行,我的做法,让这些tabbaritem继承一个自定义的tabbaritem,在awakefromnib方法里写自定义样式,如下

1
2
3
4
5
6
// 设置高亮
self.selectedImage = [self.selectedImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
// 设置默认
self.image = [super.image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
// 设置偏移
self.imageInsets = UIEdgeInsetsMake(8, 0, -8, 0);

除此之外,手写的自定义View也能应用到Storyboard上,不过开放非常耗时,只写过一个验证码的输入框,在此就不展开说了。