skip to content
仙人掌主题

iOS应用开发:开发一款AI语音记账软件(基于 Qwen Omni)

/ 27 min read

提示:开发iOS应用需要使用Mac电脑,并下载Apple的官方集成开发环境(IDE) —XCode。所以如果你只有Windows电脑,这期教程可能你没法进行。

1. 项目概要

本期教程我们来尝试不写一行代码开发一个iOS应用,用户可以通过语音的方式来实现输入要记账的内容。AI会自动解析你的语音输入,分析出当中的购买的类目的描述、价格, 并且自动实现分类。

通过这个教程,你将会学习到:

1. 使用Cursor生成产品的原型图

2. 使用Cursor自动编写SwiftUI的代码,开发iOS应用

3. 使用Python框架Flask + Supabase编写后端,并且接入Qwen Omni的全模态模型

需要说明的是,使用cursor开发一个具备完整前后端功能的iOS应用,整体的难度要比web开发是要大不少的,所以大家要有一定的心理预期。特别是在后端接入的部分,debug的过程很长。这个视频的当中你也会看到不少我用Cursor去 debug的技巧,同时我也会教给大家如何的去梳理一个后端技术的流程。

2. 生成项目原型

这一小节我们首先来使用Cursor生成对应的基于HTML代码的原型图。需要注意的是,即使是同一套提示词,AI生成出来的原型图也可能会有差异,AI生成后如果和自己的预期有差异的,需要和AI沟通反复调整。

大家也可以直接下载我生成好的原型图文件,直接跳到第3部。

prototype.zip 16.2KB

为什么要先用使用Cursor生成原型图?

这在我们之前小程序的几期课程中有使用到,我们首先会让Cursor + Cluade的模型帮我生成HTML的原型图。生成原型主要有2个好处:

• 帮助我们理解项目样式和交互

• 帮助之后Cursor能够使用SwiftUI复刻我们的原型

创建一个空文件夹 prototype,拖入到Cursor中打开这个文件夹。使用Claude-4- Sonnet的模型 +Agent模式,在Agent模式下输入下面提示词:

项目描述:
开发一款语音记账iOS应用,项目总共有4个tab:记账/统计/历史/设置。
1. 记账:主界面为记账页面,顶部会显示本月支出,下方会有两个按钮,分别是语音输入和手动输入按钮,下方会显示今日的的记录历史。用户长按语音输入后,能够录音。点击手动输入后,弹出popup要求用户输入金额、标题、时间(默认当前时间)和选择分类
2. 统计:统计页面可以切换月/季度/年,然后显示对应的支出情况。并且显示两个柱状图,分别是趋势图(显示用户的花销,按时间顺序)和分布图(显示用户分类花销,从高到底)
3. 历史:按天显示历史记录
4. 设置:用户可以选择货币单位(人命币、美元、欧元)、对分类进行管理(真删改查)、对数据实现管理(有导出CSV数据的按钮,和清楚所有数据的按钮)。
请按照以下步骤完成所有界面的原型设计:
1. 用户体验(UX)分析
任务:分析该 App 的核心功能与用户需求,确定主要交互逻辑。
要求:深入研究播客应用的用户行为,聚焦于发现、播放、订阅及社交分享等关键场景,确保交互流程直观、高效,并符合用户习惯。
2. 产品界面规划
角色:产品经理
任务:定义 App 的核心界面,构建合理的信息架构。
要求:设计清晰的界面层级和导航体系,确保用户能够快速访问主要功能,同时保持整体结构的简洁性和可理解性。
3. 高保真 UI 设计
角色:UI 设计师
任务:基于真实的 iOS 设计规范,打造现代化的高保真界面。
要求:
严格遵循 iOS Human Interface Guidelines
严格遵循 iOS 人类界面指南
注重视觉设计的统一性,包括色彩搭配、排版规范及图标风格。
使用真实的 UI 图片(可从 Unsplash、Pexels 或 Apple 官方 UI 资源获取),避免使用占位符图像,确保设计具备良好的视觉体验。
4. HTML 原型实现
技术要求:
使用 HTML + Tailwind CSS(或 Bootstrap)开发所有原型界面。
集成 FontAwesome(或其他开源 UI 组件)以提升界面的精美度和真实感。
代码结构:
每个界面以独立 HTML 文件形式存储,例如 home.html、profile.html、settings.html 等。
index.html 作为主入口,不直接包含所有界面的完整代码,而是通过 <iframe> 嵌入各界面文件,并在 index 页面中平铺展示所有页面,避免使用链接跳转。
真实感增强:
界面尺寸需模拟 iPhone 15 Pro 的屏幕规格,并应用圆角设计,以贴近真实移动设备的外观。
5. 交付物
提供完整的 HTML 代码,确保其可直接用于开发阶段。
代码文件结构清晰,命名规范,便于开发团队理解和后续维护。
注意事项:
所有设计和代码实现需符合现代移动应用的标准,兼顾视觉美观性、技术可行性以及未来的可扩展性。

稍等片刻后,AI应该就会生成这样的原型图。当然如果有和自己的预期不太符合的,我们需要进一步和Cursor沟通实现调整。

3. 开发iOS客户端前端部分

在进入这部分教程之前,如果你的电脑上还没有安装XCode,你需要先到App Store中安装XCode。

3.1 新建项目

在Mac中下载XCode,打开Xcode,并点击 [Create New Project] 创建一个新的项目

我们要开发的是iOS App。选择平台为iOS,选择应用为App。

在 Product Name 中填入对应的产品名称,并在storage 中选择SwiftData。点击 【Next】,确认保存的文件目录即可。

什么是SwiftData?

SwiftData 可以帮你做到:

• 把数据保存在本地(如:用户的记账记录、待办事项、草稿等)

• 支持自动存储、查询、删除和更新数据

• 和 SwiftUI 无缝配合,比如你可以用 @Query 实时绑定界面数据

这个时候你就创建成功了你的第一个iOS应用。

3.2 用Cursor开发前端

3.2.1 基于原型图生成前端代码

想刚才新创建的项目文件夹拖动到Cursor中,用Cursor打开项目:

将我们上一步创建好的原型图的文件夹拖动到项目根目录下,修改文件夹的名称为 prototype,表示这是原型图页面。

接下来在Cursor的Agent模式下输入对应的提示词,使用 claude-4-sonnet-thinking 的模型。等待AI生成完成

请你根据 @/prototype 下面的原型图,创建对应的iOS页面,在 @/VoiceAccount 文件夹下,创建对应的app页面。目前分别有4个页面,要求尽可能的还原样式。注意 @index.html 不用还原,它是一个概览页面,只需要还原其他4个页面就好。

@文件夹和文件的操作需要自己手动选择,直接复制粘贴可能会失效哦!

3.2.2 优化+修改bug

注意:因为AI生成有一定的随机性,所以你遇到的报错信息和UI不一致的地方可能和我不太一样,你需要根据自己的实际情况和报销信息进行修改,建议看我的视频学会方法,而不是完全看下面的文档。

原则是:

1.如果有报错,将报错信息复制给Cursor。

2. 如果有和预期不一致的地方,描述清楚你希望AI修改的地方。越精准越好

等待AI创建成功原型图后,大概率你会和我视频中的一样,有几十个报错信息。这些报错属于编译错误。

等待AI创建成功原型图后,大概率你会和我视频中的一样,有几十个报错信息,这些报错属于编译错误。出现这个错误主要有两个原因:

1. Swift 是强类型+编译型语言,容错率低。

2. Cursor对于SwiftUl 的语法、作用域、结构等掌握不如JavaScript或者Python等语言

好在其实在XCode中明确给出了报错信息,遇到这些报错我们可以尝试两种办法让 Cursor实现修改:

1. 鼠标右键然后点击复制,一个一个复制报错信息给到Cursor

2. 直接截图给到Cursor这些报错信息

如果顺利的话,Cursor解决完成所有的报错信息后,你就可以点击 ContentView 这个页面,预览我们的项目成功跑起来了。

开始优化

之后我们就可以进入到真实的优化中,我们用Cursor做了多处的优化。注意:你需要优化的点可能和我们不太一样,需要根据你自己AI生成的实际情况调整,下面的提示词只是作为参考。

• 优化1:

• 提示词:

优化:
1. 现在有很多的假数据,请你修改成为真实的本地存储的数据
2. 分类管理处添加新分类的时候,没有办法编辑分类名称和对应的icon,我们选择icon和icon的背景色

• 优化2:

• 提示词:

目前这两个页面的统计图的柱状图有显示异常:
1. 支出趋势有假数据
2. 分类分布会超出高度,并且x轴的柱子需要对其最底边

• 优化3:

• 提示词:

有几个问题:
1. 季度现在统计柱状图显示异常
2. 柱状图的柱子上方增加对应的数字,最好能增加用户的交互
3. 在设置页面切换货币单位后,所有的货币单位都应该实现修改

• 优化4:

• 提示词:

设置页面的导出CSV数据功能没有完成,我希望点击后能够将所有的本地数据导出。

4. 初始化Flask + Supabase

iOS客户端的前端部分开发完成后,接下来我们来开发后端部分。首先我们需要对这个产品的后端逻辑实现分析,最终我们产出了这样的一个后端流程图。

经过技术选型和调讲,我们最终选定了这样的技术方案:

1. 后端框架使用Python的Flask

2. 用户录音存储服务,我们使用supabase的Storage实现存储

3. 使用Qwen Omini模型实现音频的AI解析工作

4.1 初始化Flask

1. 创建一个新的文件夹,叫做 VoiceAccountServer

2. 用Cursor打开刚才创建好的文件夹

3. 在命令行中创建虚拟环境

• 终端命令

python3 -m venv venv

4. 激活虚拟环境

• 终端命令

source venv/bin/activate

5. 安装flask

• 终端命令

pip install flask

安装成功后,在Cursor的Agent模式下输入以下提示词,让其创建一个接口

• 提示词:

创建一个flask接口,能够返回hello flask

6. 在命令中输入 python app.py,运行起来Flask项目

• 终端

python app.py

注意,这里要在虚拟环境中运行。注意前面需要有 venv 的显示字样。

问:为什么要使用虚拟环境?

答:虚拟环境让你在每个项目中使用独立的 Python包,不会互相影响,也不会污染系统环境。

然后你可以访问 http://localhost:5000/(端口需要根据实际调整,我项目中修改成为了9001端口,默认应该会创建5000的端口)

4.2 接入Supabase

接下来我们来接入supabase,用于保存我们用户的录音功能。

1. 新建supabase项目

新建supabase项目,获取到对应的Project URL和服务端私钥

• Project URL

• 服务端私钥

2. 创建环境变量文件

在 /server 目录下,创建一个 .env 文件。将刚才获取到的项目url和服务端私钥填入。

SUPABASE_URL=your_project_url
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key

同时在项目的根目录下创建一个.gitignore文件,用于隐藏掉Git对应.env 文件的版本管理。避免该隐私文件被上传到了Github上。

3. 让Cursor接入Supabase

提示词:

在`app.py`中获取`.env`中的环境变量,并初始化supabase。`SUPABASE_URL`和`SUPABASE_SERVICE_ROLE_KEY`为supabase的Project URL和服务端私钥,使用服务端私钥初始化.
并且新建一个`/supabase-test`接口,该接口通过`supabase.auth.admin.list_users`获取用户数量。来测试是否接通supabase

4. 重新安装依赖

在命令行中(venv的虚拟环境)输入一下命令,更新对应的环境变量:

pip install -r requirements.txt

5. 启动项目并测试

重新在命令行中输入python app.py,运行起来项目。然后在supabase中点击【Add user】添加用户。创建一个新的用户

然后访问 http://localhost:9001/supabase-test , 如果顺利你会发现你的Flask应用成功连接了Python。

5. 开发后端业务

上一步我们只是把后端的基础能力和框架搭建起来了,接下来我们来买现后端的业务需求。这一部分会比较难,同时提示词也会比较专业。

5.1 优化文件夹结构

在开始之前,我们先把客户端代码和后端代码放到同一个父文件夹中,然后用Cursor打开:

1. 父文件夹是 VoiceAccount

2. 客户端代码的文件夹修改成为 VoiceAccountClient

3. 服务端代码的文件夹修改成为 VoiceAccountServer

这样用Cursor同时打开 voiceAccount 这个父文件夹,Cursor就能同时拥有前后端的上下文,方便同时让Cursor修改。这算是一个小技巧。

5.2 文件上传服务

5.2.1 新建存储桶

在Supabase的Storage中点击【New Bucket】按钮,新建一个存储桶,叫做 user-audio。同时设置为Public bucket。

5.2.2 开发存储录音服务

现在我们来开发存储录音服务,当用户点击录音的时候,能够上传录音到supabase的 storage中,方便之后Al解析。

提示词:

1. 用户点击录音按钮后,需要实现录音,结束录音的时候,保存录音文件为.m4a的文件格式并发送给后端接口
2. @app.py 中新增一个接口,用于接受录音文件,然后将录音文件保存到supabase的文件存储桶中,对应的bucket名称为user-audio。并且返回对应的url

等Cursor修改成功之后,你可能需要安装一些依赖并重启一下Python的后端服务,打开终端,分别输入下面的命令:

cd VoiceAccountServer
source venv/bin/active
pip install -r requirements.txt
python app.py

如果在终端看到项目运行起来了,那么就表示后端服务重启成功了。

这一步的调试我们需要编译,使用模拟器实现调试。

这个时候会出现一个具有真机功能的这样一个模拟器,我们使用它实现测试

点击语音输入然后点击录音按钮。如果顺利的话,你就可以在supabase的storage中看到你刚才上传的那个音频文件了。就说明这个功能开发完成了。

5.3 接入后端AI服务

现在我们完成了录音的存储服务,并且获取到了录音的远程url。这个时候我们就可以来接入我们对应的后端服务了。也就是架构图中下面这部分。

5.3.1 选择模型 +获取API Key

我们选择阿里云百炼当中的【通义千问-Omni-Turbo】模型,这个模型是一个全模态模型,支持文本/图像/音频/视频的解析。

首先我们点击API-KEY,创建一个对应的API-KEY

然后复制创建好的API KEY,将其填入到我们的 VoiceAccountServer/.env 文件中, 在.env中新增 DASHSCOPE_API_KEY,填入你刚才在阿里云百炼中获取到的API KEY

.env:

SUPABASE_URL=your_supabase_url
SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key
DASHSCOPE_API_KEY=dashscope_api_key

5.3.2 开发AI解析功能

接下来我们进入到Qwen-Omini的API文档中,找到音频+文本输入部分的示例代码。这段代码能够作为Cursor接入AI功能的重要参考。

然后再Cursor中输入下面的提示词

• 提示词

```
import os
from openai import OpenAI
client = OpenAI(
# 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:api_key="sk-xxx",
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
completion = client.chat.completions.create(
model="qwen-omni-turbo-0119",
messages=[
{
"role": "user",
"content": [
{
"type": "input_audio",
"input_audio": {
"data": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250211/tixcef/cherry.wav",
"format": "wav",
},
},
{"type": "text", "text": "这段音频在说什么"},
],
},
],
# 设置输出数据的模态,当前支持两种:["text","audio"]、["text"]
modalities=["text", "audio"],
audio={"voice": "Cherry", "format": "wav"},
# stream 必须设置为 True,否则会报错
stream=True,
stream_options={"include_usage": True},
)
for chunk in completion:
print(chunk)
# if chunk.choices:
# print(chunk.choices[0].delta)
# else:
# print(chunk.usage)
```
1. 请你参考上面的代码,在python中增加一个新的接口,用于将/upload-audio接口上传的语音url地址通过该接口的方式解析我们的记账明细,要求AI返回金额/标题/分类的json数组的形式,如果有多条需要返回多条。接口中需要上传语音url地址和客户端中保存的分类数据,方便AI自动选择对应的分类。
2. 客户端需要新增该接口的调用逻辑,当/upload-audio接收到语音url后立即调用该接口,并开始AI解析的状态提示,移除掉现有的url地址的显示,要求显示一个好看的AI解析中的动画效果。在接受到AI的返回值后呢,能够进行渲染我们记账的条目,并且用户能够自己编辑金额/标题/分类/时间(时间默认客户端当前时间),并实现统一保存

等待AI增加完前后端的逻辑之后,我们就可以开始进行测试了。如果遇到相关的Bug,我们就像之前做的那样,把报错信息告诉Cursor。如果有相关的和预期不太符合的地方, 我们也把现在呈现的现状,以及我们的预期描述发送给Cursor,让Cursor来实现修改。

这一步我们的报错Debug的时间很久,大家可以查看我们的视频,看一下我是如何让 Cursor一步一步修改完成这些报错的。

注意,这一步同样需要在模拟器中查看,点击编译按钮。你就可以打开模拟器。在调试过程中,注意打开控制台面板。来查看对应的日志信息,方便我们查看整个AI语音解析的流程。(如果你发现没有日志,你可以让AI添加上对应的日志信息)

5.4 增加Logo

我们的产品现在还没有Logo,那么如何给产品添加上对应的Logo呢?也很简单,你只需要在XCode中点击Assets,然后拖入你自己创建好的Logo就好

注意Logo的规格是1024×1024,同时目前有3个Logo,分别代表下面几个意义。你可以根据自己的需要分别传入不同的Logo图片。当然在我们的教程中为了省事,就传入了同样一个Logo。

现在我们的记账的数据是通过SwiftData进行本地存储的。一旦用户更换了手机就无法查看记账的历史数据了。你可以尝试一下将记账的数据存放在supabase的数据库中,让AI 新增一个数据表,然后来编写对应的提示词帮你完成这个数据存放到数据库的逻辑吧!

如何实现真机调试?

如果你想实现真机测试工作,用你的iPhone连接到你的手机,同时需要在你的iPhone上开启开发者服务才行。在连接手机的时候,你会遇到两个问题,否则无法实现手机调试:

1. 需要在iPhone上开启开发者服务

2. 编译成功后,需要授权开发者证书才能够打开App

如何在iPhone上开启开发者服务?

1. 在【设置】中点击【隐私与安全性】

2.找到【开发发者模式】(Developer Mode),并打开

3. 手机会自动重后,并要求你验证

如何解决无法打开应用的开发者证书的问题?

1. 在【设置】中搜索 【VPN 与设备管理】

2. 找到对应的证书

3. 信任开发者APP的对应的证书

同时你要修改请求地址,使用局域网的请求地址。

ipconfig getifaddr en0

搜索项目中的 http://localhost,找到请求地址,将其替换成为IP地址,并确保你的手机和电脑处于同一个局域网下。就可以用有线连接你的手机进行测试了。

6. 项目源码

6.1 客户端代码(内含HTML原型图代码)

VoiceAccountClient.zip 4.5MB

6.2 后端代码

VoiceAccountServer.zip 14.1MB

6.3 iOS上架流程