iOS 开发为什么普遍不用“拖拉拽”可视化 UI#
🔥 前言#
先纠正一个容易误解的前提:现代 iOS 开发不是“完全不用可视化 UI”,而是很多中大型项目不再把 Storyboard / XIB 当作页面结构的唯一事实来源。现在更常见的是“代码声明界面 + 实时预览 + 自动化验证”,其中 SwiftUI 本身就是这种变化的代表。
拖拉拽解决的是“快速摆出一个界面”;工程团队真正长期面对的,却是多人协作、需求变化、组件复用、动态布局、代码审查、自动化测试和跨版本维护。两者关注的问题不在同一个尺度上。
一、四种 UI 方式先分清 🔼 🔽#
1.1、它们不是“可视化”和“代码”两个简单阵营#
| 方式 | 界面的主要事实来源 | 可视化能力 | 常见定位 |
|---|---|---|---|
| Storyboard | XML 场景图、约束、Segue | Interface Builder | 小型应用、原型、流程固定的页面 |
| XIB | 单个 View / Controller 的 XML | Interface Builder | 独立视图、历史 UIKit 项目 |
| 纯代码 UIKit | Swift / Objective-C 源码 | 运行、Debug View Hierarchy、第三方预览 | 高度动态、组件化或历史大型项目 |
| SwiftUI | 声明式 Swift 源码 | Xcode Preview | 新功能、跨 Apple 平台、状态驱动 UI |
Storyboard 和 XIB 仍由 Xcode 支持;“大家不用拖拉拽”通常只是团队在架构上的取舍,不是 Apple 删除了这条开发路线。
1.2、SwiftUI 不是“没有可视化”,而是可视化方式变了#
SwiftUI 把界面结构写在源码中,Preview 再把结果实时渲染出来。开发者可以在源码、预览、控件库和属性检查器之间联动,但最终可审查、可合并的事实仍然是代码。
这相当于把过去的:
拖控件 → 改 XML → 运行后确认变成:
声明 View → Preview 即时反馈 → 测试和源码共同验证二、中大型项目为什么偏向代码式 UI#
2.1、XML 能比较,但不容易“读懂变化”#
Storyboard / XIB 本质上仍是 XML 文件。移动一个控件、改一次约束或让 Xcode 升级文档格式,都可能同时改变对象 ID、层级、Frame 和元数据。
代码式 UI 的差异通常更接近人的意图:
VStack(spacing: 12) {
titleView
submitButton
}代码审查者能直接看出“新增了什么、删掉了什么、布局参数为什么变化”。这不代表代码永远没有冲突,而是冲突通常更容易定位、拆分和讨论。
2.2、多人同时修改同一张画布,合并成本高#
一个 Storyboard 可能同时承载多个页面、Segue 和共享对象。两个人只要改到同一个 XML 文件,就可能发生冲突;Git 能指出文本冲突,却不知道哪一个控件关系才是产品意图。
团队可以通过“每个页面一个 Storyboard”降低冲突,但继续拆分之后,它已经逐渐接近代码组件化的管理方式。
2.3、真实页面不是一张固定海报#
商业 App 常同时面对:
- 不同 iPhone / iPad 尺寸、横竖屏和分屏。
- 动态字体、VoiceOver、粗体文本和辅助功能尺寸。
- 中文、英文、阿拉伯语等不同文本长度和书写方向。
- 登录态、会员态、AB 实验、远端配置、空态、异常态和骨架屏。
- 列表复用、分页、异步数据和局部刷新。
Interface Builder 可以表达 Auto Layout,但当页面结构本身由状态决定时,代码通常更直接。SwiftUI 更进一步,把“状态改变后界面应该长什么样”写成声明。
2.4、组件化需要清楚的输入、输出与依赖#
拖出来的控件容易依赖 Outlet、Segue、Storyboard ID 和隐式生命周期。规模变大后,调用方需要记住很多“画布之外的约定”。
代码组件更容易明确:
- 初始化需要哪些数据。
- 哪些状态由组件自己维护,哪些由外部注入。
- 用户事件通过闭包、Delegate、Binding 还是 Action 回传。
- 样式、无障碍标识和埋点在哪一层统一。
这也是设计系统、模块化和可复用组件更偏爱源码表达的原因。
2.5、自动化测试和持续集成更喜欢确定性#
代码式 UI 可以被编译器、Lint、单元测试、Snapshot Test、UI Test 和 CI 一起检查。Storyboard 也会参与构建,但很多连接错误要到运行或加载场景时才暴露,例如 Outlet 失效、复用标识不一致、场景类名变化等。
SwiftUI Preview 还能构造多组静态状态,用较低成本同时查看深色模式、动态字体、语言和设备尺寸。Preview 不是测试的替代品,但能让视觉反馈更靠近源码修改时刻。
2.6、导航和依赖注入不应藏在一根连线里#
Storyboard Segue 对简单跳转很直观,但复杂工程通常需要:
- 登录拦截与深链恢复。
- A/B 路由和模块解耦。
- 父子流程编排。
- 页面依赖注入与可测试替身。
当跳转规则成为业务逻辑后,显式 Router / Coordinator 或 Navigation 状态,比散落在画布里的连线更容易追踪。
三、拖拉拽 UI 仍然适合什么场景#
3.1、不是落后,而是要用在合适的尺度#
下列场景继续使用 Storyboard / XIB 完全合理:
- 产品原型、教学 Demo、一次性验证。
- 页面数量少、结构稳定、协作者很少的应用。
- 设计师或初学者需要快速建立控件与约束直觉。
- 历史项目已有成熟的 Storyboard 分包、代码审查和测试规范。
- 单个可复用 UIKit View 用 XIB 表达,边界明确且不频繁变动。
问题不在“拖拉拽”三个字,而在于是否把一个会长期演进的复杂系统,压进一张越来越难合并和审查的画布。
3.2、Interface Builder 仍有自己的优势#
- 约束关系和层级能直接观察。
- 对固定表单和静态页面,初始搭建速度快。
- 不运行完整 App 就能看到大致结构。
- Size Class、Safe Area、Stack View 等能力仍可视化配置。
所以合理结论不是“纯代码一定高级”,而是:团队应按页面动态程度、协作规模和维护周期选择事实来源。
四、为什么很多团队从 Storyboard 迁出#
4.1、常见的真实触发点#
| 触发点 | 表面现象 | 根因 |
|---|---|---|
| 合并冲突频繁 | XML 冲突难判断 | 多页面共享同一文件 |
| 启动或页面加载慢 | 大 Storyboard 初始化成本上升 | 场景和对象图过大 |
| 复用困难 | 同一组件被复制多份 | 组件边界与输入不明确 |
| 线上状态复杂 | 画布只有一套静态样子 | UI 由运行时状态决定 |
| 测试困难 | 必须加载整个场景 | 构造函数和依赖隐式化 |
| 模块拆分困难 | Storyboard ID 跨模块耦合 | 路由和所有权没有显式接口 |
4.2、迁移不应该一次推倒重来#
更稳妥的路线是:
1、先停止继续扩大单个 Storyboard。
2、把新增页面写成独立的 SwiftUI 或代码式 UIKit 组件。
3、用 UIHostingController、容器控制器等方式让新旧页面共存。
4、优先迁移冲突最频繁、状态最复杂、测试收益最大的页面。
5、保留已经稳定且维护成本低的 XIB / Storyboard,不为了形式统一强拆。
五、团队应该怎样选#
5.1、一张简单决策表#
| 问题 | 偏向 Storyboard / XIB | 偏向代码 UIKit / SwiftUI |
|---|---|---|
| 页面是否高度动态 | 否 | 是 |
| 是否多人并行修改 | 少 | 多 |
| 是否跨模块复用 | 少 | 多 |
| 是否要求状态预览和快照测试 | 低 | 高 |
| 团队现有能力 | IB 流程成熟 | 代码架构成熟 |
| 项目剩余生命周期 | 短且稳定 | 长期迭代 |
5.2、Jobs 式结论#
现代 iOS 团队减少拖拉拽,不是因为可视化“不专业”,而是因为:
页面在设计阶段像一张图,在工程阶段却是一个会被多人持续修改、由数据驱动、需要测试和版本管理的程序。
代码式 UI 把结构、状态和变化收敛到同一套可审查事实中;SwiftUI Preview 又把可视化反馈补回来。今天真正的趋势不是“可视化消失”,而是“可视化从 XML 画布,迁移到以代码为源的实时预览”。