转载

Storyboard的爱与恨(下)

Scene的转场

如我们所料,Storyboard也可以通过可视化的操作来实现Scene的转场。

故事板的转场有两种,可以分为手动触发和自动触发。自动触发完全由Storyboard实现,而手动触发则需要配合代码。前者简单易用,后者适用于配合业务逻辑,进行不同转场的触发。自动触发的转场非常简单,我们只需选择一个UIControl(比如UIButton),按住Control+左键,拖线至目标Scene,选择 Action 类型,即可在触发UIControl的某些事件的时候,自动执行转场。 Storyboard的爱与恨(下)

例如利用UIButton转场,实际上是在触发 TouchUpInside 事件时执行。这一简单的操作实际上相当于如下代码:

- (void)viewDidLoad {     [self.button addTarget:self                     action:@selector(showPSViewControllerB)           forControlEvents:UIControlEventTouchUpInside];  }  - (void)showPSViewControllerB {     PSViewControllerB *viewController = [[PSViewControllerB alloc]init];     //配置..传值...     [self.navigationController pushViewController:viewController animated:YES]; } 

Storyboard将Scene转场变成了可视化的操作又引入了一个新的问题,需要如何传递参数给目标ViewController?

解决方法就是,我们需要在Storyboard中给Segue一个Identifier,然后在源ViewController中重写如下方法即可:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {     if ([segue.identifier isEqualToString:[PSViewControllerB description]])     {         PSViewControllerB *vc = segue.destinationViewController;         //配置..传值..     } } 

手动触发则需要代码配合。不同的是,拖线的对象从UIControl变成了UIViewController(不要忘了在Storyboard中填写Segue Identifier)。

Storyboard的爱与恨(下)

然后在代码中需要转场的地方,加上 performSegueWithIdentifier:sender: 即可。例子如下:

//self:PSViewControllerA     if (isBizSuccess){         [self performSegueWithIdentifier:[PSViewControllerB description] sender:parameter];     } else {         [self showTips:@"some failure reason"];     } 

你可以利用 performSegueWithIdentifier:sender: 来进行任何形式的转场。Segue为我们的转场提供了不同的Action,囊括了常见的UINavigationViewController的push,或者所有ViewController都可以执行的Modelly Presentation。

事实上,在iOS 8以后,我们就可以利用Storyboard结合代码实现自定义的转场,无论是在哪一种上下文环境中。

采用Storyboard进行Scene转场的好处在于,一个ViewController的所有转场代码,都集中到了 prepareForSegue:sender: 方法中,debug或者添加新功能时,可以很容易顺藤摸瓜。但缺点同样明显。每次转场的修改/删除需要同时修改Storyboard和代码文件。同时,随着项目的进行,越来越多的Scene和业务逻辑,导致Storyboard中Segue的数量剧增,难以维护。

巨量的Segue(仅仅是部分截图)

Storyboard的爱与恨(下)

多Storyboard协作

解决如上问题的方法就是,尽量将项目的界面分割在多个Storyboard文件中。一个最佳实践是,按照项目功能模块来区分故事版,例如 Login.Storyboard , Chat.Storyboard , Person.Storyboard 等。尽量把每个Storyboard的Scene数量控制在20个以内。

同时,Scene间的转场我们依然可以采用Segue,并且使用起来和单个Storyboard无异。这要多亏Apple在iOS 9新推出的 UIStoryboard Reference 。

代码可视化

还有什么能比代码可视化更加炫酷的呢?作为前端工程师,最享受的时候,就是枯燥的代码和算法变成了优美的动画。但这一切都只在按下command+R之后。

现在,通过Storyboard,我们也可以在编译时实时预览我们的代码所产生的效果。

IB_DESIGNABLE

Storyboard的爱与恨(下)

通过为自定义的View添加 IB_DESIGNABLE 关键字(注意图中关键字的位置),我们让Storyboard为我们自定义的视图进行实时渲染。有的人可能会担心实时渲染造成的性能问题。这点大可放心,Xcode有一套非常优秀的缓存机制(优秀到有些时候必须要clean一下,某些小改动才会在真机上生效),只需要编译一次,视图就会被缓存,不会造成每次在Storyboard、代码文件中切换时多次渲染的问题。

在swift中则为@IBDesignable,放在class关键字之前

到这里令人惊叹的类似Playground的事实渲染功能,已经可以动态地应用在项目中了。我们可以利用IB_DESIGNABLE和IBInspectable来制作图表等高度自定义的、独特的视图。

当然,故事板狂魔对故事板的使用不会就此罢手的,本着一切能用Storyboard配置就不写代码的原则,我们也希望可以在故事板中配置自定义控件的属性。幸运的是,Apple再次为我们的想法提供了可能。

IBInspectable

Storyboard的爱与恨(下)

通过为自定义View的属性添加 IBInspectable 关键字(注意图中关键字的位置),我们可以将原本需要代码配置的属性,放到故事板中。IBInspectable支持以下类型的属性:

  • BOOL
  • NSString
  • NSNumber
  • CGPoint
  • CGSize
  • CGRect
  • UIColor
  • NSRange
  • UIImage

在swift中则为@IBInspectable,放在var关键字之前

为系统控件添加IBInspectable

不少设计设都喜欢设计圆角。通常我们需要写如下代码:

    view.layer.cornerRadius = 5;     view.layer.masksToBounds = YES; 

为了解决这些重复代码的问题,有的人喜欢为View写Category,一行代码实现圆角。然而这需要在不同的ViewController中不断引入这个Category,不够优雅。当然,这种小事情我们也肯定不会愿意采用继承的。

实际上,我们只需要为项目添加一个View的Category,并声明一个@property并加上IBInspectable关键字,然后在实现文件中,实现具体的逻辑。 不用import头文件,也不需要运行 ,Storyboard中将自动出现这个属性以供配置。这不正是我们梦寐以求的完全解耦吗!?

//UIView+CornerRadius.h @interface UIView (CornerRadius)  @property (nonatomic, assign) IBInspectable CGFloat cornerRadius;  @end 
//UIView+CornerRadius.m @implementation UIView (CornerRadius)  - (void)setCornerRadius:(CGFloat)cornerRadius {     self.layer.cornerRadius = cornerRadius;     self.layer.masksToBounds = cornerRadius > 0; }  - (CGFloat)cornerRadius {     return self.layer.cornerRadius; } @end 

Storyboard的爱与恨(下)

实际上,IBInspectable是对运行时属性进行的一种拓展,你在Attributed Inspector中进行的自定义属性配置,都会在Identity Inspector的运行时属性中得到体现。

Storyboard的弊端

Storyboard也并非十全十美的。它依然有许多的问题亟待解决,有些致命的问题,更是成为导致许多开发者放弃Storyboard的原因。在iOS9普及率已经达到 77% 的今天,Storyboard仍然有很多问题需要完善。

难以维护

Storyboard在某些角度上,是难以维护的。我所遇到过的实际情况是,公司一个项目的2.0版本,设计师希望替换原有字体。然而原来项目的每一个Label都是采用Storyboard来定义字体的,因此替换新字体需要在Storyboard中更改每一个Label。

幸亏我们知道Storyboard的源文件是XML,最终写了一个读取-解析-替换脚本来搞定这件事。

性能瓶颈

当项目达到一定的规模,即使是高性能的MacBook Pro,在打开Storyboard是也会有3-5秒的读取时间。无论是只有几个Scene的小东西,还是几十个Scene的庞然大物,都无法避免。Scene越多的文件,打开速度越慢(从另一个方面说明了分割大故事版的重要性)。

让人沮丧的是,这个造成卡顿的项目规模并不是太难达到。

我猜想是由于每一次打开都需要进行I/O操作造成的,Apple对这一块的缓存优化没有做到位。可能是由于Storyboard占用了太多内存,难以在内存中进行缓存。Whatever,这个问题总是让人困扰的。

然而需要指出的是,采用Storyboard开发或采用纯代码开发的App,在真机的运行效率上,并没有太大的区别。

错误定位困难

Storyboard的初学者应该对此深有体会。排除BAD_EXCUSE错误不说,单单是有提示的错误,就足以让人在代码和Storyboard之间来回摸索,却无法找到解决方案。

一个典型的例子是,在代码中删除了IBOUTLET属性或者IBAction方法,但是却忘了在Storyboard中删除对应的连接,运行后crash。然而控制台只会输出一些模糊其词的错误描述。

*** Terminating app due to uncaught exception 'NSUnknownKeyException',  reason: '[<DrawViewController 0x7fe9f6a11240> setValue:forUndefinedKey:]:   this class is not key value coding-compliant for the key drawButton.'   

有经验的开发者可以从drawButton这个关键字中找到突破口,但大部分刚接触Storyboard的开发者,会被困在其中。

最后

综合其利弊,毅然选择了站在Storyboard这边。一方面是其提供的便利,另一方面是Apple对Storyboard的大力支持。这一点宏观上看,可以在以往对Storyboard的改进和增强上看出,微观上看,几乎所有iOS 8之后的simple code都或多或少采用了Storyboard作为界面开发工具。有理由相信,Storyboard的未来是光明的。愿大家在Storyboard的路(keng)上,越走越远。

原文  http://shengpan.net/storyboard2/
正文到此结束
Loading...