[译]iOS用户界面:Storyboards vs. NIBs
我常听见iOS程序员问类似这样的问题:
iOS开发界面的最好的方法是:Storyboard,NIBs,还是代码?
对这个问题的各种答案,似乎明示或暗示着,必有一个唯一的选择,这个选择往往要在开发之前就要做出。
我的看法,这个问题的答案应该是以一个或多个类似问题的形式来给出。
哪款车是“最好的”?
来举一个题外的例子。假如我想买一辆车,我问你一个“简单”问题:“我的最佳选择是什么?”
你能真的给我建议一个车型,甚至是一个品牌来回答我的问题吗?恐怕不能。除非你建议“法拉利”。相反,你更可能会通过问一些问题来回答:
- 你有多少钱?
- 你需要几个座儿?
- 你对油耗有多在意?
- 你觉得越野车怎么样?
很显然,除非在特定前提下,否则没有“好车”或“坏车”之分——只有在需求确定的前提下才有好车、坏车之分。
回到iOS UI设计问题
就像刚才选车的问题一样,“开发iOS UI的最好的方法是什么”这个问题也缺少特定前提。令你意外的是,这个问题的答案也不需要解决所有问题。
概括地说,有三种开发UI的方法可供你选择,并且每一种都各有长短,各有粉丝和吐槽者。
- iOS Storyboards: 一个可视化的工具,可以放置多个views以及他们之间的过渡
- NIBs(or XIBs): 每个NIB文件对应于一个单独的view元素,可以放置在Interface Builder中,故也算一个可视化工具。注:“NIB”这个名字来源于文件后缀(以前是.nib,现在是.xib,尽管现在还是读“NIB”)
- 纯代码: 没有GUI工具,用程序的方式来处理所有的位置设置、动画等效果
这些选择中,任何一个都不比另外的一个“更好”(尽管你可能听说过哪个更好)。
例如:Storyboards,是最迟加入到iOS UI工具包中的。我曾听说“他们将是未来,将替代NIBs和代码UI”。但是,我只把Storyboards看作一个利器,而不是一个完全替代NIBs和代码的替代者。Storyboards在某些场合是正确的选择,但也不是所有场合。
toptal-blog-image-1396377741412.png进一步说,如果你(在一个工程中)可以使用任意一个,最恰当地解决某个特定问题,那为什么要保守一个选择不放呢?
在我的观点中,这个问题可以提升到一个更高层次,其答案在我的一系列“软件开发原理”中排名还很靠前,这个答案就是:没有哪一种万能的语言,万能的框架或技术对软件开发中的任意问题都是绝对最好的解决方案。 iOS UI设计问题也是如此。
本篇iOS开发引导中,我们将逐一介绍以上每一种方法,以及哪些场合适用,哪些场合不适用,已经哪些场合兼而用之。
iOS Storyboards
新手常犯的一个典型错误是:创建一个工程级巨大的Storyboard,当然,我刚开始用时也犯了这个错误。
新手常犯的一个典型错误是:创建一个工程级巨大的Storyboard。一个Storyboard上是呈现了一个“故事”,而不应该将不相关的很多故事混合在一起
顾名思义,一个Storyboard就是一个“有故事要讲”的板,不应该将不相关的很多故事混合在一起。一个Storyboard应该包含一系列在逻辑上有关联的view controller,而不是所有。
举例来说,处理下列问题是使用Storyboard是合理的:
- 授权和注册所需的一系列views
- 由多步组成的订阅入口
- 类似于引导的事务流
- 此处不会译
同时,应避免太大的Storyboard,也要避免整个App就一个Storyboard(除非App相对比较简单)。在我们深入探讨之前,先来看看为什么。
超大Storyboard的不便之处
超大的Storyboard,不但很难浏览、很难维护,还给团队引入了一层复杂性:当多个程序员同时在一个Storyboard工作时,代码冲突就在所难免。因为Storyboard本质上是文本文件(xml),merge往往不是一件很容易的事。
程序员看代码时,是通过代码的语义来理解。所以手工合并时,可以读懂冲突的两边文件,并相应处理。而Storyboard是由xcode管理的xml,每行代码的往往并不好弄明白。
举一个很简单的栗子:假如两个程序员都改变了一个UILabel(使用了自动布局)的位置,后提交(试图提交)的取到已提交者的代码,就会产生下面的冲突(注意已冲突的id属性):
<layoutGuides>
<viewControllerLayoutGuide type="top" id="ar5-ed-tdC"/>
<viewControllerLayoutGuide type="bottom" id="enX-PS-xZQ"/>
</layoutGuides>
<layoutGuides>
<viewControllerLayoutGuide type="top" id="6cK-2r-Dvq"/>
<viewControllerLayoutGuide type="bottom" id="EbB-ky-ghh"/>
</layoutGuides>
id属性本身没有提供任何有用的信息,所以你不知道该怎么做。唯一合理的做法是从两边选一个、舍弃另一个。但这样会不会有神马“副作用”呢?谁知道?反正你不知道。
�为了使UI设计问题简化,建议在一个工程中使用多个Storyboard。
何时使用Storyboard
Storyboard最好被用于彼此有联系的多个view controller时,这样主要是能简化view controller直接的切换。一定程度上,他们可以被看做NIBs的可视化和view controller之间切换的组合。
除了简化切换,另一个显著的好处是可以省去一些代码,诸如:pop,push,present 以及 dismiss view controllers。另外view controller是自动分配,所以无需alloc init
最后,虽然说最好是应用于“多个”view controllers的场景,但对“单个”table view controller应用Storyboard也是蛮好的。原因有3:
- 可以当场设计tabel cell原型,所有的东西都在一起
- 多个cell模板可以在父table view controller中设计
- 使创建static table views成为可能
有人会说,多个cell模板也可以用NIBs来设计。当然,这只是个人喜好问题:有的人喜好所有东西都放在一起,有的人却无所谓。
何时不要 用Storyboard
下面这些情况:
- 如果view有很复杂的Layout,或动态Layout,最好用代码实现
- 如果view已经用NIB或代码实现
【译者:下面这段我不是太理解,就大概翻译一下】
这些情况,要么把view从storyboard提出去,要么把他嵌入到view controller中。前一种做法,破坏了storyboard的“可视化流程”(visual flow),但是没有任何实现上的负面影响。后一种做法,保持了可视化流程,但是需要额外的开发工作,因为view不是集成到view controller中的:它只是作为一个组件嵌入进来,所以view controller必需和它进行交互。
一般的优点和缺点
现在我们对“在iOS UI设计中,storyboard何时有用”有了一个大概了解,在开始了解NIBs前,在过一般storyboard的优势和劣势
优点:性能
你可能会觉得,加载一个storyboard时,它所有的view controllers会马上被实例化。然而,这只是一种抽象,实际实现并非如此,相反,只是初始(initial)view controller(如果有的话)被创建。其它的view controller动态地实例化,无论是通过segue,还是代码实现。
优点:原型
用storyboard可以简化用户界面和流程的原型和组织。事实上,一个由views和导航组成的完整、可工作的原型应用程序,用storyboard很容易实现——只需要不多的代码
缺点:复用性
对于移动和复制,storyboard的表现就不太好了。一个storyboard必须和它所依赖的view controllers一起移动。换句话说,一个单独的view controller不能被单独抽取出来作为一个单独的实体在其它地方使用,它依赖于storyboard的其它部分才能工作。
缺点:数据流
应用程序运行时,经常需要将数据在view controller之间传递。然而,storyboard的可视化流程在这种情况下会被破坏,因为在Interface Builder中无法传数据。storyboard负责处理view controller之间的切换,但却不处理数据流。所以,“目标”controller必须用代码“配置”,这样就破坏了“可视化”。
这些情况下,我们不得不依赖于prepareForSegue:sender,再加上if/else-if:
(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSString *identifier = [segue identifier];
if ([identifier isEqualToString@"segue_name_1"]) {
MyViewController *vc = (MyViewController *) [segue destinationViewController];
[vc setData:myData];
} else if ([identifier isEqualToString@"segue_name_2"]) {
...
} else if ...
}
我认为这种方式很容易出错,而且也过于繁琐。
NIBs
NIBs是(较)老的设计UI的方式。
在这里,“老”并不意味着“坏”、“过时”、“不推荐”。事实上,应该深刻理解:storyboard并不是能完全取代NIBs的方式,它们仅仅是在某些情况下的合适方式。
使用NIB,一个单独的view被设计出来,然后根据需要把它“附加”到view controller上去。
如果我们用“面向对象”的方式来设计UI,那么就应该把view controller’s view拆分成独立的模块,每一个模块是一个由单独的NIB文件设计的view(或者多个模块组合到同一个文件)。这样做明显的优势是:每一个元素都易开发、易测试、易调试。
NIBs也有像storyboard那样的“合并冲突”问题,但相对来说较不严重,因为NIB所操作的范围要小。
何时用NIBs设计UI
部分可用的场合如:
- 模式views【译者:那些弹出的对话框之类吧】
- 简单的登录、注册views
- 设置
- Popup windows
- 可复用view模板
- 可复用tabel cell模板
然后……
何时不要用NIBs
下面这些情况应避免使用NIBs:
- 如果view中有动态内容,且根据内容的不同Layout会有较大差异
- 如果view天然就不容易用Interface Builder设计
- 如果view controllers有复杂的transition,但用storyboard方式可以简化
一般的优点和缺点
让我们来看看NIBs的优缺点。
优点:复用性
如果多个类共用相同的Layout,那么NIBs是很方便的。
举个简单的栗子。
【译者:这个栗子是关于登录和注册view复用同一个NIB,因为他们都由username+password组成。不是太会翻译,故略过】
优点(也是缺点):性能
NIBs是“懒加载”,所以直到加载之前是不占内存的。这是一个优势,但同时,因为懒加载过程有开销【这里不太会译】,所以也是一个劣势。
代码(用程序实现UI)
任何可以用storyboard或NIBs实现的UI都可以用纯代码实现(当然,曾经一度,没有那么多工具时就是这样的)
NIBs和storyboard不能实现的,都可以用代码实现。
也许更重要的是,NIBs和storyboard不能实现的,都可以用代码实。当然,那是代码的技术特性。换一个角度,NIBs和storyboard本身就是代码实现的,所以它们的功能天然就是代码全部功能的一个子集。现在直接来看代码的优点和缺点吧。
优点:本质
用代码实现UI的最大的好处是:如果你知道如何用代码来实现UI,那么你就知道背后发生的事情,可是NIBs和storyboard却不一定。
类比一下:计算器是一个有用的工具。但是,学会手工计算也是一个不错的事。
不只是iOS,任何可视化RAD(Rapid Application Development)工具(比如:Visual Studio、Delphi),Visual HTML RAD开发环境算是最差的代表:它们可以产生代码(当然,往往是很差的),声称“不需要html只是”,一切都可以可视化完成。但是,没有一个web程序员能“不用弄脏手”【译者:指的是不用写代码】就可以写出网页来,因为他们知道手写的html和CSS可以产生更模块化、更有效的代码。
所以,掌握用代码构建iOS UI可以让你有更多控制,也更明白各部分是如何拼接起来的,进而提高你作为一个程序员的“上限”
优点:但代码是唯一的选择时
有一些场景,只能用代码构建UI。动态Layout——可视元素动来动去,Layout根据内容有较大调整,是典型的例子。
优点:合并冲突
NIBs和storyboard饱受合并冲突之苦,而代码却完全不受此苦。所有代码都是有意义的,所以解决冲突跟其他(代码冲突)情况是一样一样的。
缺点:原型
直到代码运行起来之前,你看不到一个Layout长的什么样。更甚,你不能可视化地摆放views和controls,所以,将Layout文档(specs)转为可见的view需要更多时间,而NIBs和storyboard却可以马上给你一个可以预览的效果。
缺点:重构
重构很久之前写的代码或别人的代码——代码中元素被放置,或动画时用的自定义的方法或奇怪的数字,将会是一个很费劲的活,调试也一样费劲。
优点:性能
性能方面,NIBs和storyboard是先加载、解析,最后再翻译成代码。不用说,代码构建UI不会有此转换过程。
优点:复用
任何代码构建的view都可以用“可复用”的方式来实现。来看几个例子:
- 两个或更多views共用一个行为,但又有些微的区别。可以通过一个基类和两个派生类来解决此问题,很优雅。
- 有时不得不拆分一个工程,目的是基于同一套代码,产生两个(或更多)不同的应用程序,每一个有自己特殊的定制【译者:这个例子有点莫名其妙】
如果是用NIBs或storyboard来实现这同样的过程,将会复杂的多。模板文件【就是那些定义UI的xml文件吧】不支持继承,所以可能的解决方案也就是下面这些:
- 复制NIB和storyboard文件。从此以后,他们将开始不同的“生命”,跟原始文件也没有了关系。
- 用代码改写(override)可视效果和行为。这种方式,在简单情况下或许奏效,但复杂了就可能导致异常的复杂性。大规模的override可能导致原先用NIBs或storyboard实现的UI效果完全无用,如,某个控件在Interface Builder中表现是一种行为,而在程序运行起来之后却完全是另外一种行为,这时你就会相当头疼。
何时使用代码
当有下列情形时,用代码是不错的选择:
- 动态Layout
- views有诸如圆角、阴影等特殊效果
- 其他,如果用NIBs或storyboard会很复杂或不方便的情况
何时不用代码
总体来说,代码构建UI何时都可以用。它们“鲜有”成为错误选择的情况。
……
同一个工程,多种工具
storyboard,NIBs和代码是构建UI的三种不同的工具。很幸运,我们都可以选择。对于坚持只用代码的那些家伙来说,另外两个工具根本不会考虑,因为用代码可以实现任何技术上可能的东西,而另外两个都有它们的局限性。但对于其他的程序员,xcode这把“军刀”提供了三种不同的工具,而且可以在同一个工程中任意选用。
你会问,那我怎么选择?看你自己喜好吧。这里有一些建议供参考:
- 将相关的所有页面归到一组(这样你会有多个组),每一组实现在独立的storyboard中
- 将不可复用table cells放在storyboard中——table view controller中
- 将可复用的table cells放在NIBs中——以鼓励复用,并避免重复劳动,用代码来加载这些NIBs
- 用NIBs来设计自定义的views、controls等
- 用代码构建“很动态”的views,更泛一点说,就是不容易用storyboard或NIB是实现的views。
【译者:下面还有一个简单的例子,就不译了】
译完此文,想起自己在实际开发中的经历,有几点想说的:
——
主题色的故事
在起初开发阶段,UI没有给出主题色,我们就是按自己喜欢瞎来的。到了最后才给,我们只能逐个view检查,把NIBs和storyboard中的颜色值替换。这换一遍人还可以承受,就怕那天UI同学很追求完美,觉得“咦,这个颜色还是差那么一点点,我再微调一下吧”。哥呀,我就崩溃了。
这个故事触发我想,还是得写在代码中(以这个主题色为例),如果UI要换,我只要改一个变量值(一行代码哦)就够了。当然,我也没有办法抗拒NIBs在设计简单、固定页面时的便利。于是,我就又引入一个机制:在每一NIB所对应的UIView中提供一个机会,将主题色等这种需要全局统一的UI元素重新设一次(也就是作者在文中提到的override)。也就是说,最终在UI上的显示效果将由此处代码的设置为最终结果,NIB文件中的被覆盖(那就当做程序员开发过程中的“第一层防锈漆”吧)
适配屏幕的故事
为了适应各种size的手机屏,有些页面得采用“占页面百分比”的方式来摆放UI元素。这种情况,只能用code实现了。因为最终的Point坐标是在运行时计算出来的,用NIB和storyboard是断不可能实现的。当然,这样的页面一般也是比较简单(就是因为UI元素少,才需要根据屏幕大小调整一下位置、大小等)