iOS 用户界面:故事板、NIB 和自定义代码
已发表: 2022-03-11我经常听到 iOS 开发者问同一个关键问题的一些变体:
在 iOS 中开发 UI 的最佳方式是什么:通过 Storyboard、NIB 或代码?
对这个问题的回答,无论是明确的还是隐含的,都倾向于假设有一个相互排斥的选择要做,一个通常在开发之前预先解决的选择。
我认为答案应该采取一个或多个反问的形式。
什么是“最好”的汽车?
让我用一个题外话的例子来解释。 假设我想买一辆车,我问你一个简单的问题:“最好的选择是什么?”
你真的可以通过推荐一个模型,甚至一个品牌来回答吗? 不太可能,除非你推荐法拉利。 相反,您可能会回答一些其他问题,例如:
- 你的预算是多少?
- 你需要几个座位?
- 你关心油耗吗?
- 你对跑车有什么看法?
很明显,没有好车或坏车之类的东西,除非它被放置在适当的环境中——只有基于特定需求的好车或坏车。
返回 iOS UI 设计
就像我们的汽车查询一样, “开发 iOS UI 的最佳方法是什么”问题缺乏上下文。 令人惊讶的是,答案不一定是包罗万象的。
从广义上讲,您可以采用三种类型的用户界面设计方法,每种方法都有其优点和缺点,它的粉丝和讨厌者:
- iOS Storyboards : 一种用于布置多个应用程序视图以及它们之间的转换的可视化工具。
- NIB (或 XIB) :每个 NIB 文件对应一个视图元素,可以在 Interface Builder 中进行布局,使其也成为可视化工具。 请注意,名称“NIB”是从文件扩展名派生的(以前是 .nib,现在是 .xib,尽管保留了旧的发音)。
- 自定义代码:即没有 GUI 工具,而是以编程方式处理所有自定义定位、动画等。
这些选项都没有比任何其他选项普遍更好(尽管您可能会听到)。
例如,故事板是 iOS UI 工具包的最新成员。 我被告知它们是未来,它们将取代 NIB 和自定义代码 UI。 我认为 Storyboards 是一个有用的工具,但与其说是替代品,不如说是对 NIB 和自定义代码的补充。 在某些情况下,故事板是正确的选择,但不是所有情况。
此外,当您可以全部使用它们(在同一个项目中)时,为什么要静态地坚持一个选项,选择最适合手头特定问题的机制?
在我看来,这是一个可以在更高层次上概括的问题,其答案在我的软件开发原则列表中排名靠前:没有通用的语言、框架或技术是每个人的通用最佳选择软件开发问题。 iOS UI 设计也是如此。
在本 iOS 开发教程中,我们将探索这些方法中的每一种,并介绍应该使用和不应该使用它们的用例,以及可以将它们混合在一起的方式。
iOS 故事板
一个经典的初学者错误是创建一个庞大的项目范围的 iOS 故事板。 当我第一次开始使用 Storyboards 时,我也犯了这个错误(可能是因为它是一条诱人的路线)。
顾名思义,故事板是一个有故事要讲的板。 它不应该被用来将不相关的故事混合成一个大卷。 故事板应该包含逻辑上相互关联的视图控制器——这并不意味着每个视图控制器。
例如,在处理时使用 Storyboard 是有意义的:
- 一组用于身份验证和注册的视图。
- 多步骤订单输入流程。
- 类似向导(即教程)的流程。
- 一组主从视图(例如,配置文件列表、配置文件详细信息)。
同时,应避免使用大型 Storyboard,包括单个应用范围的 Storyboard(除非应用相对简单)。 在我们深入之前,让我们看看为什么。
大型 iOS 故事板的愚蠢之处
大型 Storyboard 除了难以浏览和维护之外,还给团队环境增加了一层复杂性:当多个开发人员同时处理同一个 Storyboard 文件时,源代码控制冲突是不可避免的。 虽然情节提要在内部表示为文本文件(实际上是 XML 文件),但合并通常并非易事。
当开发人员查看源代码时,他们将其赋予语义意义。 因此,当手动合并时,他们能够阅读和理解冲突的双方并采取相应的行动。 相反,故事板是由 Xcode 管理的 XML 文件,每行代码的含义并不总是很容易理解。
让我们举一个非常简单的例子:假设两个不同的开发者改变了UILabel
的位置(使用自动布局),而后者推动了他的改变,产生了这样的冲突(注意冲突的id
属性):
<layoutGuides> <viewControllerLayoutGuide type="top"/> <viewControllerLayoutGuide type="bottom"/> </layoutGuides> <layoutGuides> <viewControllerLayoutGuide type="top"/> <viewControllerLayoutGuide type="bottom"/> </layoutGuides>
id
本身并没有提供任何关于它的真正意义的指示,所以你没有什么可使用的。 唯一有意义的解决方案是选择冲突的两个方面之一并丢弃另一个。 会不会有副作用? 谁知道? 不是你。
为了缓解这些 iOS 界面设计问题,在同一个项目中使用多个故事板是推荐的方法。
何时使用故事板
故事板最好与多个互连的视图控制器一起使用,因为它们的主要简化在于视图控制器之间的转换。 在某种程度上,它们可以被认为是 NIB 的组合,在视图控制器之间具有视觉和功能流。
除了简化导航流程之外,另一个明显的优势是它们消除了弹出、推送、呈现和关闭视图控制器所需的样板代码。 此外,视图控制器是自动分配的,因此无需手动alloc
和init
。
最后,虽然 Storyboard 最适用于涉及多个视图控制器的场景,但在使用单个表视图控制器时使用 Storyboard 也是合理的,原因有以下三个:
- 就地设计表格单元原型的能力有助于将各个部分保持在一起。
- 可以在父表视图控制器内设计多个单元格模板。
- 可以创建静态表视图(一个期待已久的添加,不幸的是仅在 Storyboards 中可用)。
有人可能会争辩说,也可以使用 NIB 设计多个单元模板。 事实上,这只是一个偏好问题:一些开发人员更喜欢将所有东西都放在一个地方,而另一些则不在乎。
何时不使用 iOS 故事板
几个案例:
- 视图具有复杂或动态的布局,最好用代码实现。
- 该视图已使用 NIB 或代码实现。
在这些情况下,我们可以将视图留在 Storyboard 之外,或者将其嵌入到视图控制器中。 前者打破了故事板的视觉流程,但没有任何负面的功能或开发影响。 后者保留了这种视觉流程,但它需要额外的开发工作,因为视图没有集成到视图控制器中:它只是作为组件嵌入,因此视图控制器必须与视图交互而不是实现它。
一般优点和缺点
现在我们已经了解了 Storyboard 何时在 iOS UI 设计中有用,在我们继续本教程中的 NIB 之前,让我们来看看它们的一般优点和缺点。
优点:性能
直观地说,您可以假设当 Storyboard 被加载时,它的所有视图控制器都会立即实例化。 幸运的是,这只是一个抽象,而不是实际实现:相反,只有初始视图控制器(如果有)被创建。 其他视图控制器是动态实例化的,无论是在执行 segue 时还是从代码中手动实例化。
临:原型
故事板简化了用户界面和流程的原型设计和模拟。 实际上,一个完整的带有视图和导航的原型应用程序可以使用 Storyboard 和几行代码轻松实现。
缺点:可重用性
在移动或复制方面,iOS Storyboards 的定位很差。 故事板必须连同其所有相关的视图控制器一起移动。 换句话说,单个视图控制器不能作为单个独立实体单独提取和在其他地方重用; 它依赖于情节提要的其余部分来发挥作用。
缺点:数据流
当应用程序转换时,通常需要在视图控制器之间传递数据。 但是,在这种情况下,故事板的视觉流程被破坏了,因为在 Interface Builder 中没有发生这种情况的痕迹。 Storyboard 负责处理视图控制器之间的流程,但不负责处理数据流。 因此,必须使用代码配置目标控制器,从而覆盖视觉体验。
在这种情况下,我们必须依赖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 ... }
我发现这种方法容易出错并且不必要地冗长。
笔尖
NIB 是执行 iOS 界面设计的旧方法。
在这种情况下,“旧”并不意味着“坏”、“过时”或“已弃用”。 事实上,重要的是要了解 iOS Storyboard 并不是 NIB 的通用替代品。 在某些情况下,它们只是简化了 UI 实现。

使用 NIB,可以设计任意视图,然后开发人员可以根据需要将其附加到视图控制器。
如果我们将面向对象的设计应用于我们的 UI,那么将视图控制器的视图分解为单独的模块是有意义的,每个模块都实现为具有自己的 NIB 文件(或将多个模块分组到同一个文件中)的视图。 这种方法的明显优势是每个组件更容易开发、更容易测试和更容易调试。
NIB 共享我们在 Storyboard 中看到的合并冲突问题,但程度较轻,因为 NIB 文件以较小的规模运行。
何时在 iOS UI 设计中使用 NIB
所有用例的一个子集是:
- 模态视图
- 简单的登录和注册视图
- 设置
- 弹出窗口
- 可重复使用的视图模板
- 可重复使用的表格单元格模板
同时…
何时不使用 NIB
您应该避免将 NIB 用于:
- 具有动态内容的视图,其中布局根据内容发生显着变化。
- 本质上不容易在 Interface Builder 中设计的视图。
- 具有复杂过渡的视图控制器可以通过 Storyboard 进行简化。
一般优点和缺点
更一般地说,让我们来看看使用 NIB 的优缺点。
优点:可重用性
当多个类共享相同的布局时,NIB 会派上用场。
作为一个简单的用例,包含用户名和密码文本字段的视图模板可以使用假设的TTLoginView
和TTSignupView
视图来实现,这两个视图都可以源自同一个 NIB。 TTLoginView
必须隐藏密码字段,并且两者都必须指定相应的静态标签(例如“输入您的用户名”与“输入您的密码”),但这些标签将具有相同的基本功能和类似的布局。
优缺点:性能
NIB 是延迟加载的,因此它们在必须使用内存之前不会使用内存。 虽然这可能是一个优势,但延迟加载过程存在延迟,这也使其成为一个缺点。
iOS 自定义代码(程序化 UI)
任何可以使用 Storyboard 和 NIB 完成的 iOS 界面设计也可以使用原始代码来实现(当然,曾经有一段时间,开发人员没有如此丰富的工具集)。
也许更重要的是,NIB 和 Storyboard 无法完成的事情总是可以用代码来实现——当然,前提是它在技术上是可行的。 另一种看待它的方式是,NIB 和 Storyboard 是用代码实现的,所以它们的功能自然是一个子集。 让我们直接进入优缺点。
优点:引擎盖下
以编程方式创建 iOS UI 的最大优势:如果您知道如何编写用户界面,那么您就知道幕后发生了什么,而对于 NIB 和 Storyboard 则不一定如此。
进行比较:计算器是一个有用的工具。 但是知道如何手动执行计算并不是一件坏事。
这不仅限于 iOS,还适用于任何可视化 RAD 工具(例如,Visual Studio 和 Delphi,仅举几例)。 可视化 HTML RAD 环境代表了一个典型的边界案例:它们用于生成(通常写得不好)代码,声称不需要 HTML 知识,并且一切都可以可视化完成。 但是,任何 Web 开发人员都不会在不亲自动手的情况下实现网页:他们知道手动处理原始 HTML 和 CSS 将导致更模块化、更高效的代码。
因此,掌握 iOS 用户界面的编码可以让您更好地控制和了解这些部分如何组合在一起,从而提高您作为开发人员的上限。
Pro:当代码是唯一的选择时
在某些情况下,自定义 iOS 代码是 UI 设计的唯一选择。 动态布局是典型的例子,其中视图元素被移动,流程或布局根据内容进行显着调整。
优点:合并冲突
虽然 NIB 和 Storyboard 遭受合并冲突的严重影响,但代码没有同样的错误。 所有代码都具有语义含义,因此解决冲突并不比平常更困难。
缺点:原型设计
在您实际看到布局之前,很难弄清楚布局的外观。 此外,您无法直观地定位视图和控件,因此将布局规范转换为有形视图可能需要更长的时间,而 NIB 和故事板可以让您立即预览事物的渲染方式。
缺点:重构
重构很久以前或其他人编写的代码也变得更加复杂:当使用自定义方法和幻数定位和动画元素时,调试会话可能会变得很艰巨。
优点:性能
在性能方面,Storyboards 和 NIBs 会受到加载和解析的开销; 最后,它们被间接翻译成代码。 不用说,使用代码制作的 UI 不会发生这种情况。
优点:可重用性
任何以编程方式实现的视图都可以以可重用的方式设计。 让我们看几个用例:
- 两个或多个视图共享一个共同的行为,但它们略有不同。 一个基类和两个子类优雅地解决了这个问题。
- 一个项目必须被分叉,目的是创建一个单一的代码库,但生成两个(或更多)不同的应用程序,每个应用程序都有特定的定制。
对于 NIB 和 Storyboard,相同的 UI 设计过程会复杂得多。 模板文件不允许继承,可能的解决方案仅限于以下几种:
- 复制 NIB 和 Storyboard 文件。 之后,他们分别生活,与原始档案没有任何关系。
- 用代码覆盖外观和行为,这可能在简单的情况下有效,但在其他情况下可能会导致严重的复杂性。 代码的大量覆盖也会使视觉设计变得无用,并演变成令人头疼的问题,例如,当某个控件在 Interface Builder 中以一种方式显示时,但在应用程序运行时看起来完全不同。
何时使用代码
当您有以下情况时,使用自定义代码进行 iOS 用户界面设计通常是一个很好的选择:
- 动态布局。
- 带有效果的视图,例如圆角、阴影等。
- 任何使用 NIB 和 Storyboard 的情况都很复杂或不可行。
何时不使用代码
通常,始终可以使用代码制作的 UI。 他们很少是一个坏主意,所以我会放一个
尽管 NIB 和 Storyboard 带来了一些优势,但我觉得没有合理的缺点可以列出来阻止代码使用(也许除了懒惰)。
一个项目,多种工具
故事板、NIB 和代码是构建 iOS 用户界面的三种不同工具。 我们很幸运拥有它们。 程序化 UI 的狂热者可能不会考虑其他两个选项:代码允许您做技术上可能的所有事情,而替代方案有其局限性。 对于其他开发人员来说,Xcode 军刀提供了三种工具,可以在同一个项目中有效地同时使用它们。
怎么样,你问? 随你喜欢。 以下是一些可能的方法:
- 将所有相关屏幕分组到单独的组中,并使用自己独特的 Storyboard 来实现每个组。
- 在表格视图控制器内使用情节提要设计不可重用的表格单元格。
- 在 NIB 中设计可重用的表格单元以鼓励重用并避免重复,但通过自定义代码加载这些 NIB。
- 使用 NIB 设计自定义视图、控件和中间对象。
- 将代码用于高度动态的视图,更一般地用于通过 Storyboard 和 NIB 不易实现的视图,同时在 Storyboard 中容纳视图转换。
最后,让我们看一个将它们联系在一起的最后一个示例。
一个简单的用例
假设我们要开发一个具有几个不同视图的基本消息应用程序:
- 关注的朋友列表(带有可重复使用的单元格模板,以使 UI 在未来列表中保持一致)。
- 配置文件详细信息视图,由单独的部分组成(包括配置文件信息、统计信息和工具栏)。
- 发送给朋友和从朋友接收的消息列表。
- 一个新的消息表单。
- 一个标签云视图,显示用户消息中使用的不同标签,每个标签的大小与其使用次数成正比。
此外,我们希望视图按如下方式流动:
- 单击关注的朋友列表中的项目会显示相关朋友的个人资料详细信息。
- 配置文件详细信息显示配置文件名称、地址、统计信息、最新消息的简短列表和工具栏。
为了实现这个 iOS 应用,我们所有的三个 UI 工具都会派上用场,我们可以使用:
- 具有四个视图控制器(列表、详细信息、消息列表和新消息表单)的故事板。
- 用于可重用配置文件列表单元模板的单独 NIB 文件。
- 配置文件详细信息视图的三个单独的 NIB 文件,一个用于组成它的每个单独部分(配置文件详细信息、统计信息、最后三个消息),以实现更好的可维护性。 这些 NIB 将被实例化为视图,然后添加到视图控制器。
- 标签云视图的自定义代码。 此视图是无法在 Interface Builder 中设计的典型示例,既不能通过 StoryBoard 也不能通过 NIB。 相反,它完全通过代码实现。 为了维护 Storyboard 的视觉流程,我们选择在 Storyboard 中添加一个空的视图控制器,将标签云视图实现为独立视图,并以编程方式将视图添加到视图控制器中。 显然,视图也可以在视图控制器内部实现,而不是作为独立视图,但我们将它们分开以便更好地重用。
一个非常基本的模型可能如下所示:
至此,我们概述了一个相当复杂的 iOS 应用程序的基本结构,其核心视图将我们的三种主要 UI 设计方法联系在一起。 请记住:没有二元决定,因为每个工具都有其优点和缺点。
包起来
正如本教程中所研究的,Storyboards 为 iOS UI 设计和视觉流程添加了显着的简化。 他们还消除了样板代码; 但所有这一切都是有代价的,以灵活性为代价。 与此同时,NIB 通过专注于单一视图提供更大的灵活性,但没有视觉流程。 当然,最灵活的解决方案是代码,它往往相当不友好,而且天生就不是视觉的。
如果这篇文章引起了您的兴趣,我强烈建议您观看 Ray Wenderlich 的精彩辩论,花 55 分钟讨论 NIB、故事板和代码制作的 UIS。
最后,我想强调一件事:不惜一切代价避免使用不当的 iOS UI 设计工具。 如果一个视图不能用 Storyboard 设计,或者如果它可以用 NIB 或代码以更简单的方式实现,不要使用 Storyboard。 同样,如果不能使用 NIB 设计视图,请不要使用 NIB。 这些规则虽然简单,但对您作为开发人员的教育大有帮助。