AI 动力的 iOS 开发 — 使用 ChatGPT #1 构建生产就绪功能
ISBN 扫描仪
介绍
大家好!我正在开始一系列文章,展示在生产准备就绪的iOS应用程序上使用ChatGPT的实际例子。为什么这很有意思?因为我之前没有iOS开发经验。我学到的有关Swift和iOS的一切都是来自ChatGPT。现在,我正在比之前在我创业公司雇佣的iOS开发者编码更快。
小背景(你可以直接跳到下一段的技术内容)
大约两年前,我和我的妻子一起发布了我们的应用程序Uncover的Android版本。我们很幸运地聚集了超过12,000个用户,但我们意识到我们的大多数用户在美国,因此我们知道iOS版本的应用程序是绝对必不可少的。一开始,我尝试完全依靠Chat的帮助来构建它,但我失败了:更多信息请看这里。
太长不看:我们决定雇佣承包商来建立应用程序的第一个版本,但现在我们正准备将其发布到App Store(目前正在进行测试版测试。如果有兴趣的人 - 这里是等待名单),但在测试过程中,我们意识到有一些功能被简单地忽略了。其中一个功能是ISBN扫描仪。
那我们在建造什么呢?
非常简单的事情:基本上允许用户扫描他们书背面的ISBN码,然后将其带到我们应用程序内部的图书详情页。这简化了搜索图书的过程,特别是如果您正在根据拥有的实体书籍构建To Be Read列表(我们应用程序的一个功能之一)。 在Android版本的Uncover中非常受欢迎,但在iOS开发过程中被忽视了。
起初我想简单地把这个推给我的承包商,但是…
嗯.. 預算並不是那麼好笑 😂,因為 Chat GPT 01 剛剛出現,我決定試一試… 讓我告訴你:自從我寫第一篇文章以來,人工智慧已經有了很大的改進。這是一個12步的成功故事,所以準備好了。
- 我开始使用Chat GPT o1提示。 这是我解决复杂问题的首选工具。 对于简单的故障修复和简单的更改,我使用Chat GPT 4o,但每当我遇到完整功能或更复杂故障时,我会选择o1。它的推理帮助我开始正确的路径,然后我简单地切换到Chat 4o,继续改进初始想法。所以我从这个精确的提示开始。
我想在我的Swift UI应用程序中创建一个功能。它针对iOS 15,所以请在任何与版本相关的解决方案中考虑到这一点。这个功能被称为ISBN扫描器,想法是在我的搜索输入框中点击一个图标(请使用Google字体中的barcode_scanner图标),我们应该打开一个屏幕,让用户扫描一个条形码(EAN13)。
如果代码不正确(扫描失败,或代码不是完整的13位代码),请显示一个带有通用错误信息的 snackbar — 我的 viewModel 中有一个名为 showSnackBar 的方法,可以使用它 func showSnackBar(text: String, style: SnackBarModel.SnackbarStyle = .error)。
- 如果代码正确,请调用一个名为GET v1/books/by-isbn/的端点。它将返回书籍模型的列表.
- 如果第一个请求的响应为空,请调用GET v2/books/new-isbn/。它也会返回Book模型的列表
在ViewModel中,当您仅接收到一个模型的列表时,请导航到书籍详情页面。为了导航到这个视图,我使用这个目的地:BookDetailsView(viewModel: BookDetailsViewViewModel(searchingBookItem: model), shouldPopToRootView: .constant(false))
在 ViewModel 中,当您收到包含更多模型的列表时,请选择第一个模型,然后导航到 BookDetails 屏幕。
请使用RecommendationsView和RecommendationsViewModel作为包含搜索输入的视图和视图模型。
我使用存储库模式和请求类,请跟随这个模式(您可以在ViewModel中使用searchingDataRepository: SearchingDataRepository,并在存储库中使用SearchBookByISBNRequest)。
我使用Alamofire进行网络请求,请使用它。
如果此扫描需要某些权限(例如相机权限),请确保在点击ISBN按钮时向用户请求这些权限,如果用户尚未授予这些权限。
2. 那是 Chat 的回答
当然!以下是根据您的要求实现的ISBN扫描器功能。代码包括:
在RecommendationsView中将一个条形码扫描器图标添加到搜索输入框中。当用户点击ISBN按钮时,处理摄像头权限。使用AVCaptureSession实现条形码扫描功能。使用Alamofire向指定的端点发起网络请求。根据响应导航到BookDetails页面。使用showSnackBar显示错误处理的snackbar。请查看下面的代码:
3. 我在RecommendationsView中直接添加了我的ISBN扫描仪的聊天功能,但我意识到我有自定义的SearchView,所以我不得不在那里添加它,并从我的RecommendationsView传递一个监听器。
import SwiftUI
struct RecommendationsView: View {
@ObservedObject var viewModel: RecommendationsViewModel
var body: some View {
NavigationView {
VStack {
// Search input with barcode scanner icon
HStack {
TextField("Search", text: $viewModel.searchText)
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
Button(action: {
viewModel.scanISBN()
}) {
Image(systemName: "barcode.viewfinder") // Using SF Symbols as a placeholder
.font(.title)
.padding()
}
}
.padding()
Spacer()
}
.sheet(isPresented: $viewModel.isShowingScanner) {
BarcodeScannerView { result in
viewModel.handleScanResult(result)
}
}
.background(
NavigationLink(
destination: BookDetailsView(
viewModel: BookDetailsViewViewModel(searchingBookItem: viewModel.selectedBook),
shouldPopToRootView: .constant(false)
),
isActive: $viewModel.navigateToBookDetails
) {
EmptyView()
}
)
}
}
}
4. 它还在我的搜索容器中添加了底部弹出表单,带有扫描器和直接导航链接。由于搜索容器只是我的屏幕上的一个自定义视图,所以我不得不将它移到屏幕的主体部分。我提到这一点是因为这需要作为开发者的我的输入。聊天并没有完美地做到。我不得不在之后清理它,但它为我启动了,并且我只需要完善解决方案。
5. 它并不完全理解存储库/请求模式,但我不怪它。我没有解释清楚。它在我的viewModel中引入了请求和存储库。看起来是这样。
let request = SearchBookByISBNRequest(isbn: isbn)
searchingDataRepository.searchBookByISBN(request: request) { [weak self] books in
guard let self = self else { return }
if let books = books, !books.isEmpty {
self.handleBooksResponse(books)
} else {
self.fetchBookByISBNV2(isbn)
}
}
我不得不手动将请求移动到存储库中,但这并不困难,如果我不这样做,它也可以工作。我只是想与我的其他请求保持一致。所以在更改之后,它看起来像这样(我还删除了回调,因为这以前不是我们使用的内容)。
// ViewModel
if let books = searchingDataRepository.searchBookByISBN(isbn: isbn) {
guard let self = self else { return }
if let books = books, !books.isEmpty {
self.handleBooksResponse(books)
} else {
self.fetchBookByISBNV2(isbn)
}
}
// SearchingDataRepository
func searchBookByISBN(isbn: String) -> BookResponse {
let request = SearchBookByISBNRequest(isbn: isbn)
return try await network.execute(request).decode(to: BookResponse.self)
}
6. 我整理了代码以匹配我的编程风格和私有方法,并生成了构建。在新文件和导入方面我遇到了一些问题,但我自己解决了这些问题。备注:我所做的所有修复都是基于我在此应用程序上与Chat一起开发其他功能/bug修复时所获得的知识。学习新技术的最佳方法是在其中解决问题。
7. 当我成功构建了应用程序并运行它时,点击扫描按钮会导致应用程序崩溃。而且错误像地狱般神秘。有一些内核问题。
8. 在这一点上,我把聊天切换到4o。我只使用o1进行最初的构思。你可以只是切换模式并继续对话,就像什么都没有发生一样。所以我继续使用这个提示。
我实现了上面的代码但是当尝试访问BarcodeScannerView时出现了一个奇怪的错误。它说:“线程8队列: com.apple.root.default-qos (concurrent),信号SIGABRT”。
9. 原来 Chat 01 只是忘了告诉我,我需要在我的 Info.plist 文件中添加相机权限。它只通过代码请求了这个权限,但这个权限也必须在“清单”中存在。一旦我添加了它,应用程序就不再崩溃了💪
10. 我检查了逻辑,扫描仪正常工作,扫描了正确的ISBN并调用了我的终端。🚀权限流程运行良好,在出现任何错误时,会显示适当的消息栏。不过,我在响应方面遇到了一些问题,所以我不得不进行更多的调查。首先,我收集了一些日志以查看哪些工作正常,哪些不正常,并向聊天请求帮助以了解发生了什么。
那帮了忙!谢谢。我在成功扫描 isbn 后遇到了代码问题。“数据无法读取,因为它不是正确的格式”。
这是我在仓库中的方法
通过ISBN号搜索书籍。
让请求 = SearchBooksByISBNRequest(isbn:isbn)
返回尝试等待网络执行(request)。解码为:BookResponse.self
}
struct SearchBooksByISBNRequest:请求 {
var路径:String = “URL”
变量parameters: 参数?
让方法: let method: HTTPMethod = .get
init(isbn: String) { parameters = [ "isbn": isbn ] }
} 保持 HTML 结构,将以下英文文本翻译成简体中文:
11. 结果是我只是简单地弄乱了网络模型。在将后端的响应复制到聊天时,聊天向我展示了这一点。修复后,我得到了一个完全可用的功能 😎
整个功能花了我1.5小时,同时为这篇文章记笔记... 承包商最初的估计是16到24小时。我会把这些数字留给你。
12. 最终结果