在实际项目中,UI 原型承担的任务很明确:在写正式业务代码之前,先把页面长什么样、状态怎么切换、交互往哪里走说清楚。过去做 UI 原型通常用 Axure 或 Figma,先画图再让开发翻译成代码;现在可以换一种方式:用 Xcode 中的智能体能力创建 UI 原型,核心做法是让编码智能体直接根据需求生成 SwiftUI 代码,再在 Xcode 的预览画布里快速查看和交互。这种方式省掉了“画图稿转开发”的中间翻译,也让 UI 原型从静态图片变成了可运行的代码。
这篇文章要完成的事情是:从一个空白 SwiftUI 工程开始,搭出一条“需求描述—智能体生成—Xcode 预览—迭代修改”的 UI 原型工作流,并给出可直接套用的提示词模板、代码示例和排查清单。文章涉及的环境以 macOS + Xcode 为主,使用的智能体可以是 Xcode 编辑器内嵌的 AI 助手,也可以是命令行编码智能体,核心工作流不绑定具体工具。先理解原理,再搭环境,最后跑通一个商品列表案例,这样换到其他页面时也能复用同一套方法。
1. 先理解智能体创建 UI 原型到底在解决什么问题
1.1 传统 UI 原型流程的成本集中在“转换损耗”
UI 原型不是最终界面,而是用来确认需求、对齐交互、验证信息架构的中间产物。传统流程一般是:产品经理写需求文档,设计师用 Axure 或 Figma 画高保真图,开发再按图写代码。这个链条中最大的问题不是某个环节做得不够好,而是每次转换都会产生损耗。
需求文字转视觉图时,设计师要猜测“这个按钮放在哪里更合理”“这里到底要不要展示空态”;视觉图转代码时,开发要判断“这个圆角是多少”“这个列表用 TableView 还是 CollectionView”。两轮转换之后,评审会上讨论的往往已经不是需求本身,而是“图上这个间距是不是太大”“这个按钮状态是不是漏了”。UI 原型本应帮助团队在早期发现问题,但画板工具的产出物和最终代码之间存在一条很宽的鸿沟。
1.2 智能体在原型阶段扮演的不是设计师,而是“能直接生成代码的实现者”
编码智能体可以理解自然语言需求,直接生成 SwiftUI 代码;可以在已有代码上修改视图结构;还能根据报错信息自动修复问题。在 UI 原型阶段,它的价值不是一次性交付一个完美页面,而是快速生成一个可运行的草稿,让团队基于草稿继续讨论。
这里要明确边界:智能体不是产品经理,也不是视觉设计师。它擅长把描述转换成确定性的代码结构,但对“好不好看”“是否符合品牌调性”这类问题判断有限。所以使用智能体之前,最好把视觉约束写清楚:间距、字号、颜色、圆角、标签状态、空态和加载态。输入越具体,生成的代码越接近预期。
1.3 为什么 Xcode 和 SwiftUI 适合作为 UI 原型的载体
Xcode 自带 SwiftUI Preview 画布,修改代码后可以立刻看到视图变化,不需要重新编译整个 App。这一点对 UI 原型极其重要,因为原型阶段的反馈速度决定了讨论质量。如果改一个间距要等三分钟编译,团队自然会减少调整次数,最终接受一个并不满意的页面。
SwiftUI 是声明式框架,界面的结构、依赖的数据、交互回调都写在一个视图里,非常接近原型要表达的内容。另一个优势是:原型如果用 SwiftUI 写,后续可以直接演进成正式工程,不用像 Figma 稿那样再翻译一次。对于内部工具页、运营活动页、电商商品展示页这类以静态数据和简单交互为主的功能,这条路尤其合适。
到这里可以总结一条主线:用智能体生成 SwiftUI 视图,用 Xcode Preview 作为画布,用模拟器验证真实交互。这个循环比“画图再开发”短得多,也是整篇文章后面要反复使用的工作方法。
2. 先搭工作流,再动手写代码
2.1 明确智能体工具落在哪一层
当前可用的智能体工具大致有三类,原型阶段不必全部掌握,先选一种能跑通的。
第一类是 Xcode 编辑器内嵌的 AI 助手,适合已经知道要做什么、需要加速写代码的场景,例如补全一个视图、解释一段报错、生成小段 SwiftUI 结构。第二类是命令行编码智能体,在项目目录下运行,可以读取代码仓库、生成文件、执行测试,适合一次性创建一批 SwiftUI 文件。第三类是智能体平台编排,例如用 Dify 之类的平台构造“需求转结构化提示词”的流程,再把结果粘贴进 Xcode,适合团队想固化需求模板时使用。
UI 原型阶段推荐第一类和第二类组合:IDE 内负责看预览和改代码,命令行智能体负责批量生成文件。下面示例不依赖某个具体商业产品,只要智能体能生成 Swift 代码、能理解“只创建这几个文件”的指令,就可以套用。
2.2 把产品需求整理成结构化输入
智能体的输出质量取决于输入质量。不要直接把一段口语化需求丢给智能体,建议先拆成四个部分:
- 页面目标:用户在这个页面要完成什么任务。
- 信息层级:哪些是主信息,哪些是辅助信息。
- 交互状态:默认态、空态、加载态、错误态,以及点击后的行为。
- 视觉约束:主色、圆角、字号、间距,至少给出一组基准值。
例如“做一个商品列表页”就不够。更合适的输入是:“做一个商品列表页,用户从上到下浏览商品卡片,点击卡片进入详情报价,列表顶部是页面标题,卡片间距 12pt,左右边距 16pt,价格用红色醒目标注。”
这套信息并不复杂,但它决定了智能体是否能生成接近预期的结构。实际项目里如果需求来自产品文档,可以直接把文档中相关的段落抽取出来,再补上视觉约束。比起口头描述,结构化输入让智能体的“自由发挥”范围变小。
2.3 建立“生成—预览—修改”的迭代循环
不要期待一次生成就满意。正常循环是:
- 整理需求并写成提示词。
- 让智能体生成一个可编译的 SwiftUI 文件。
- 在 Xcode Preview 中查看渲染结果。
- 把不符合预期的地方描述给智能体,要求只修改某个视图,不要动业务逻辑。
- 反复执行,直到满足要求。
每次修改的范围越小,智能体越稳定。如果一次要求生成一个完整复杂页面,很容易出现组件名冲突、布局溢出、可选值不匹配等问题。正确做法是先搭出一个可编译的骨架,再逐个组件细化。
注意:原型阶段追求的是快速看到效果,不要一上来就让智能体接真实接口、做登录态、加数据库。这些东西会显著拉长生成时间,也会让预览变慢。
3. 从零创建 Xcode 原型工程
3.1 环境准备与版本确认
在开始前先确认几项基础环境,避免后面预览出现奇怪问题。
| 环境项 | 建议值 | 说明 |
|---|---|---|
| macOS | 较新的正式版本 | Xcode 版本越高,对硬件的系统要求也越高 |
| Xcode | 建议不低于 Xcode 15 | SwiftUI Preview 和 #Preview 宏依赖较新工具链 |
| iOS Simulator Runtime | 按需下载 1 到 2 个 | 不需要把所有系统版本都下载下来 |
| 智能体工具 | 任选一种 | 只要能读取项目目录、生成 Swift 文件即可 |
Xcode 的版本会直接影响 SwiftUI API 选择。比如NavigationStack需要 iOS 16 及以上,#Preview需要 Xcode 15 及以上。如果设备或项目需要支持低版本系统,要提前告诉智能体,否则生成的代码在真机上可能无法运行。
3.2 创建最小 SwiftUI 工程
最稳妥的方式是直接用 Xcode 图形界面创建:
- 打开 Xcode,选择 Create New Project。
- 选择 iOS 下的 App 模板。
- Product Name 填
PrototypeApp,Interface 选择 SwiftUI,Language 选择 Swift。 - 选择本机保存目录,完成创建。
创建后先运行一次默认工程,确认模拟器、签名和预览都能正常工作,再开始让智能体加入代码。这样在 Xcode 层面已经排除了一部分环境问题。
如果希望更接近工程化实践,可以使用 XcodeGen 管理项目文件。准备一份project.yml:
name: PrototypeApp options: bundleIdPrefix: com.example targets: PrototypeApp: type: application platform: iOS deploymentTarget: "16.0" sources: - path: PrototypeApp settings: base: GENERATE_INFOPLIST_FILE: YES然后在项目根目录执行:
xcodegen generateXcodeGen 的好处是工程文件不参与源码冲突,智能体新增文件后重新生成即可。对于原型阶段的单人或小团队项目,这个方案比手动维护.xcodeproj更省心。
3.3 原型工程目录结构
智能体生成代码时,最好让它遵守一个稳定的目录结构,否则文件散乱之后很难维护。一个适合 UI 原型的结构如下:
PrototypeApp ├── PrototypeApp.swift ├── ContentView.swift ├── Models │ └── ProductItem.swift ├── SampleData │ └── SampleData.swift └── Views ├── ProductCardView.swift ├── ProductListView.swift └── ProductDetailView.swift入口文件PrototypeApp.swift和ContentView.swift尽量保持不变,智能体只需要往Models、SampleData、Views里写文件。这样即使反复修改,也不会破坏项目启动链路。
4. 用智能体生成一个商品列表 UI 原型
4.1 先给出一个具体需求描述
为了演示,设一个常见场景:商品列表页。需求描述如下。
页面要展示商品卡片列表,用户可以上下滑动浏览;每张卡片包含占位图、商品名称、可选标签和价格;点击卡片进入商品详情页,详情页展示大图、名称、价格和返回按钮。视觉上卡片使用圆角 12pt,列表内容左右边距 16pt,卡片间距 12pt,价格用红色突出。所有数据先用静态示例数据,不接接口,不做网络请求。
这段描述已经覆盖了页面目标、信息层级、交互状态和视觉约束,可以直接作为提示词的素材。
4.2 给智能体的提示词模板
把需求翻译成提示词时,要强调“保持可编译”“不使用第三方依赖”“只使用静态数据”,避免智能体顺手生成无用代码。
请用 SwiftUI 为一个 iOS 商品列表功能生成 UI 原型。 要求: 1. ProductItem 数据模型放在 Models/ProductItem.swift, 字段为 name、price、tag,其中 tag 是可选字符串, 结构体遵守 Identifiable。 2. SampleData.swift 提供 3 条静态示例数据。 3. ProductCardView 展示占位图、商品名、标签、价格, 卡片使用圆角 12pt,背景使用 secondarySystemBackground。 4. ProductListView 使用 ScrollView + LazyVStack, 列表间距 12,内容左右边距 16。 5. 点击卡片跳转 ProductDetailView, 详情页展示占位大图、商品名、价格。 6. 所有文件保持可编译,不引入第三方依赖。 7. 不要新增接口、网络请求和持久化代码。这个模板的核心是限定文件位置、数据来源和视觉参数。实际使用时,可以删掉示例中与需求无关的条目,但“保持可编译”和“不引入第三方依赖”这两条建议保留。
4.3 生成后的 SwiftUI 代码示例
根据上面提示词,智能体可能生成类似下面的代码。先看数据模型:
import SwiftUI struct ProductItem: Identifiable { let id = UUID() let name: String let price: String let tag: String? }示例数据:
struct SampleData { static let products: [ProductItem] = [ ProductItem(name: "无线办公鼠标", price: "¥129", tag: "热卖"), ProductItem(name: "机械键盘 87 键", price: "¥299", tag: "新品"), ProductItem(name: "显示器增高架", price: "¥89", tag: nil) ] }商品卡片:
struct ProductCardView: View { let item: ProductItem var body: some View { HStack(spacing: 12) { RoundedRectangle(cornerRadius: 8) .fill( LinearGradient( colors: [.blue, .cyan], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(width: 64, height: 64) .overlay( Text("图") .foregroundStyle(.white) .font(.headline) ) VStack(alignment: .leading, spacing: 6) { Text(item.name) .font(.headline) .lineLimit(1) if let tag = item.tag { Text(tag) .font(.caption) .padding(.horizontal, 6) .padding(.vertical, 2) .background(Color.orange.opacity(0.2)) .clipShape(Capsule()) } Text(item.price) .font(.subheadline) .foregroundStyle(.red) } Spacer() } .padding(12) .background(Color(.secondarySystemBackground)) .clipShape(RoundedRectangle(cornerRadius: 12)) } }列表页:
struct ProductListView: View { let items: [ProductItem] var body: some View { ScrollView { LazyVStack(spacing: 12) { ForEach(items) { item in NavigationLink(destination: ProductDetailView(item: item)) { ProductCardView(item: item) } .buttonStyle(.plain) } } .padding(16) } .background(Color(.systemBackground)) .navigationTitle("商品列表") } }详情页:
struct ProductDetailView: View { let item: ProductItem var body: some View { VStack(spacing: 16) { RoundedRectangle(cornerRadius: 16) .fill( LinearGradient( colors: [.blue, .cyan], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(height: 180) .overlay( Text("商品主图") .foregroundStyle(.white) .font(.title2) ) Text(item.name) .font(.title2) .bold() Text(item.price) .font(.title3) .foregroundStyle(.red) Spacer() } .padding() .navigationTitle("商品详情") .navigationBarTitleDisplayMode(.inline) } }入口视图:
struct ContentView: View { var body: some View { NavigationStack { ProductListView(items: SampleData.products) } } } #Preview { ContentView() }4.4 关键代码讲解
这个例子看起来简单,但已经覆盖了 UI 原型最常用的几个结构。
ScrollView + LazyVStack适合商品数量不确定的场景。LazyVStack会按需创建子视图,数据量少时和VStack没有明显区别,但数据量增加后不会一次性创建所有卡片。原型阶段如果只是为了验证布局,用VStack也没有问题。
NavigationStack是 iOS 16 之后推荐的导航容器。它和NavigationLink(destination:)配合时,不需要给ProductItem额外声明Hashable,代码最简短。如果工程要兼容 iOS 15,可以改用NavigationView,但要注意控制台会出现弃用警告。
Color(.secondarySystemBackground)是系统提供的辅助背景色,能自动适配深色模式。UI 原型阶段尽量使用系统语义色,这样当用户在模拟器切换深色模式时,页面不会出现明显阅读问题。
if let tag = item.tag是可选值解绑的标准写法。商品没有标签时直接不渲染标签组件,列表也不会因此报错。这个细节能在智能体生成代码时经常看到,理解它有助于判断生成结果是否正确。
5. 在 Xcode 中运行、预览和验证原型
5.1 打开 Preview 画布的步骤
代码生成后,第一件事不是跑模拟器,而是先看 SwiftUI Preview。
在 Xcode 中点击画布右上角的 Resume 按钮,如果看不到画布,从菜单栏选择 Editor > Canvas。画布出现后,#Preview会直接渲染ContentView的导航栈,可以操作列表点击进入详情页。
如果 Preview 一直显示加载中,先按Command + B编译整个工程。很多预览卡死其实是编译错误导致的,编译通过后预览会自动刷新。首次打开 Preview 时 Xcode 需要编译当前 scheme,耗时几十秒到几分钟不等,不要把它当成死机。
原型也是代码,不能只看截图。至少要操作一次点击跳转,确认导航闭合循环是通的。
5.2 原型验证清单
运行原型时,按下面这张表逐项验证。不需要像正式项目那样写自动化测试,但至少要手工确认一遍。
| 验证项 | 操作方式 | 预期结果 |
|---|---|---|
| 列表滚动 | 在 Canvas 或模拟器中上下拖动 | 滚动流畅,内容不被底部截断 |
| 导航跳转 | 点击第一个商品卡片 | 进入详情页,返回按钮可用 |
| 深色模式 | 在模拟器设置中切换深色外观 | 文字与背景对比清晰 |
| 动态字体 | 在模拟器设置中调大字体 | 文本可换行,卡片不被截断 |
| 空列表状态 | 把 SampleData.products 临时改为空数组 | 当前原型会显示空白页面,这是下一轮需要补的状态 |
最后一项特意写成“会显示空白页面”,因为演示代码确实没有做空态。很多 UI 原型只验证了正常数据,忘了验证空态和加载态,结果一进真实场景就露馅。智能体生成原型后,可以根据这张表把缺失状态补进下一轮提示词。
5.3 让智能体继续修改的提示词写法
预览后通常会有不满意的地方。修改时不要笼统说“这个页面不好看”,要给出具体视图名和具体参数。
保持当前数据模型和 SampleData 不变, 只修改 ProductCardView: 1. 卡片宽度撑满父视图。 2. 商品名称最多显示 2 行。 3. 标签颜色从 orange 改成 blue。 4. 卡片圆角从 12 改成 16。 5. 不要修改其他文件,不要改变点击跳转行为。“只修改某个文件”这句非常重要。智能体一次只应该完成一个小目标,这样既能减少回归风险,也方便对比修改前后差异。如果让它“顺便优化一下其他页面”,后续排查时很难定位是哪次修改导致了问题。
注意:在原型阶段,不要因为代码能编译就跳过验证。点击路径、深色模式、空状态这三项是最容易被忽略、又最容易在评审时被指出来的问题。
6. 常见问题与排查路径
6.1 Xcode 预览不显示或一直卡住
现象是 Canvas 区域没有渲染,或者一直转圈。首先执行一次Command + B,如果编译失败,先处理编译错误。编译成功后预览会自动恢复。
如果编译通过但预览仍不显示,检查当前选择的模拟器 Runtime 是否已下载。打开 Xcode 的 Settings > Components,确认有可用的 iOS Simulator Runtime。另一个常见原因是派生数据损坏,可以清理后重试:
rm -rf ~/Library/Developer/Xcode/DerivedData清理后重新打开 Xcode,构建时间会变长,但预览通常能恢复。这个操作不会删除源码,只删除构建缓存。
6.2 智能体生成的代码编译失败
最常见的报错有:未知类型、缺少参数、属性不存在、结构体没有返回 body。
智能体生成跨越多个文件的代码时,容易出现“在 A 文件里使用了 B 文件还没生成的类型”,尤其是没有遵守目录约束时。排查顺序是先看第一个编译错误,不要被后面的连环报错干扰;确认报错文件是否存在于项目中;再检查类型名是否拼写一致。
如果报错指向某个较新的 API,例如foregroundStyle、NavigationStack,需要确认部署目标版本。部署目标低时,让智能体改用兼容写法:
.foregroundColor(.red)NavigationView { ProductListView(items: SampleData.products) }修改后重新编译。不要把报错信息直接扔回给智能体然后反复重新生成,先自己读一眼错误位置,很多时候只是文件名没有加入 target。
6.3 布局和产品描述不一致
现象是圆角、间距、对齐方式与要求不同。根本原因是提示词里没有给出数字约束,或者只写了“好看一点”这类模糊词。
正确做法是把视觉参数固化成常量或直接写在提示词里。可以让智能体把常用间距定义成私有常量:
private enum Layout { static let cardSpacing: CGFloat = 12 static let pagePadding: CGFloat = 16 static let cardCornerRadius: CGFloat = 12 }后续调整布局时,只需要修改Layout中的数值,不需要逐个视图查找。这样既方便智能体理解,也方便人工调整。
如果发现提示词里有明确数值但生成结果不对,可能是智能体只关注了局部视图,没有看到全局约束。下一轮提示词可以更聚焦:“只调整 ProductCardView 的圆角为 16,其他保持不变。”
6.4 防止智能体“编造”数据
智能体生成的示例数据具有不确定性。它可能生成一个产品名、价格、标签组合,也可能在页面里加入一个并不存在的促销字段。这本身不是大问题,原型阶段重点是布局和交互。
但如果原型要拿给团队评审,编造的数据会误导讨论。做法是把数据源固定到SampleData中,并在提示词里明确要求:“所有展示数据必须来自 SampleData,不允许自己新增字段。”这样即使智能体在代码注释或页面文案里自由发挥,核心数据仍然可控。
原型评审时看到的数据应该尽量接近真实,否则讨论结论没有参考价值。对于内部工具页面,可以用脱敏后的真实字段名;对于对外产品页,至少要使用与真实数据格式一致的 mock 数据。
| 问题现象 | 排查顺序 | 处理建议 |
|---|---|---|
| 预览空白 | 编译是否通过 | Command + B 看 Errors 面板 |
| 预览一直加载 | Runtime 是否下载 | Settings > Components 检查模拟器版本 |
| 编译失败 | 第一个报错文件 | 确认文件加入 target、类型名拼写一致 |
| 布局不符 | 提示词是否有数字约束 | 补充间距、圆角、字号参数 |
| 数据不对 | 是否脱离 SampleData | 重新要求只能使用示例数据源 |
7. 可复用的原型生成模板与生产环境边界
7.1 沉淀一份团队可复用的需求模板
用智能体生成 UI 原型时,最值得沉淀的不是某一段代码,而是需求描述模板。团队里不同角色写需求的方式差异很大,有了模板,智能体生成的页面才可能稳定。
一个通用模板可以包含以下字段:
- 页面名称。
- 页面目标。
- 入口来源。
- 数据来源。
- 组件列表。
- 交互行为。
- 视觉常量。
- 状态列表:默认、空态、加载、错误。
每次让智能体生成前,按这个模板输出一段描述。时间久了,团队会形成自己的“提示词风格”,智能体生成结果的稳定性也会显著提高。这个效果不是靠某一个模型实现的,而是靠输入质量的确定性。
7.2 学习环境与生产环境的边界
智能体创建 UI 原型速度快,但它生成的是“可以看的代码”,不等于“可以上线的代码”。进入生产阶段前,至少要重新审视以下差异。
| 关注点 | 原型阶段 | 生产阶段 |
|---|---|---|
| 数据 | 静态示例数据 | 真实接口、统一模型、错误处理 |
| 安全 | 不涉及 | Token 管理、权限校验、敏感信息保护 |
| 可维护性 | 单文件快速查看 | 模块化分层、命名规范、设计规范 |
| 性能 | 小数据量 | 分页、缓存、异步加载、避免重复渲染 |
| 质量保障 | 手工查看预览 | 单元测试、UI 测试、Code Review |
| 签名与打包 | 模拟器本地运行 | 证书、描述文件、App Store 发布流程 |
代码层面也需要处理几个原型阶段忽略的问题:网络请求的错误分支、空态和加载态、无障碍标签、动态字体适配、本地化文案。智能体可以帮你搭出页面骨架,但这些工程问题必须由开发负责人逐项确认。
7.3 从原型到正式工程的过渡方式
如果原型验证通过,想把 SwiftUI 代码并入正式工程,不建议直接复制文件。更稳妥的方式是:
- 把
PrototypeApp.swift入口替换成正式 App 入口。 - 去掉
SampleData,改成 Repository 层提供数据。 - 把
Views下的组件按业务模块重新归位。 - 增加 ViewModel 或 Store 层,把视图与数据分离。
- 补充单元测试和 UI 测试,至少覆盖核心导航路径。
- 清理
#Preview中依赖真实数据源的情况。
如果团队已经有设计系统,还需要把Color(.blue)、Color(.orange)这类硬编码颜色替换成设计系统里的语义色,把间距和圆角替换成统一 Design Token。这一步不是可有可无。原型阶段为了快,硬编码可以接受;进入正式代码后,硬编码会成为后续改版的负担。
7.4 新手练习建议
如果刚接触这个工作流,不需要一开始就做复杂页面。可以选一个每天都会打开的 App 里的普通页面,例如电商商品列表、待办清单、设置页,然后按模板描述需求,用智能体生成第一版原型,再在 Xcode 里手动调整布局。
练习时重点不是让智能体一次生成完美结果,而是掌握三个判断能力:判断生成代码是否能编译运行;判断页面布局为什么不符合预期;判断哪些代码只适合原型、哪些可以进入生产。这三个能力积累起来,再复杂的页面也能拆成模板描述、智能体生成、人工修订三个步骤处理。
一句话回到核心判断:智能体生成 UI 原型的价值,是把“需求描述到可运行代码”的反馈循环缩到最短。原型阶段快比完美重要,一旦原型要进入生产,一致性、安全性和可维护性才是第一优先级。把两者边界分清,这套工作流就能真正成为日常开发里的实用工具。