ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Trae + SwiftUI 1 小时实现一个单词本 Mac App:TaoToken 统一 Key 配置与 SwiftData 验证

Trae + SwiftUI 1 小时实现一个单词本 Mac App:TaoToken 统一 Key 配置与 SwiftData 验证 1. 为什么我选择用 Trae SwiftUI 做单词本 Mac App如果你正在找一个能在一小时内跑通的 macOS 原生小项目单词本是个很合适的练手场景数据模型简单、UI 结构清晰、增删改查逻辑完整还能顺带把 SwiftData 的本地持久化跑一遍。Trae 负责把自然语言需求转成 SwiftUI 代码SwiftData 负责数据落地而 TaoToken 负责把模型调用的 Key 和 API 通道统一管起来三者配合下来一个可用的原型基本能在一次会话里成型。这篇内容面向的是有基础 Swift 语法、但没怎么写过 SwiftUI 桌面端的人。我会把重点放在三件事上Trae 里怎么配置 TaoToken 的统一 Key、SwiftData 的 Model 骨架怎么写、以及跑起来之后怎么验证增删改查真的生效。中间会给出可以直接复制的 config.toml 片段和 Model 定义你照着改改就能用。需要提前说明的是Trae 本身是编辑器侧的 AI 编程工具TaoToken 在这里扮演的是模型调用通道的角色两者是配合关系不是替代关系。你仍然需要自己理解生成的代码尤其是 SwiftData 的 Model 宏和 ModelContainer 的注入位置这两处出错的话编译能过但运行会崩。2. TaoToken 前置准备统一 Key 与 API 通道在 Trae 里调用模型绕不开配置 API 地址和 Key。TaoToken 的做法是把多个模型的调用收敛到一个 Key 上这样你在 Trae 的配置文件里只需要维护一份凭证切换模型时改模型名就行不用来回换 Key。先到控制台创建一个 API Key。打开 https://taotoken.net/console 登录后在 API Keys 页面点新建复制生成的 sk- 开头的字符串。这个 Key 只在创建时完整显示一次建议先存到密码管理器里。拿到 Key 之后需要确认两件事一是 API 的基础地址二是你要用的模型名。基础地址是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 base_url 使用。模型名可以在模型对话页面里查看当前可用的列表选一个你熟悉的就行。注意API Key 属于敏感凭证不要直接提交到 Git 仓库。建议放在本地配置文件里并把该文件加入 .gitignore。如果你后续要做长期的编码任务或者 Agent 类的自动化流程可以了解一下 Coding Plan它在调用额度和并发上会比按量计费更适合高频场景。入口在 https://taotoken.net/coding-plan 。3. 在 Trae 中配置 config.toml 的完整片段Trae 的模型配置走的是 config.toml 文件。不同版本的 Trae 配置文件位置略有差异一般在用户目录下的 .trae 文件夹里或者通过设置界面的「打开配置文件」直接跳转。找到之后把下面这段贴进去替换掉你自己的 Key。# Trae 模型通道配置 # 统一走 TaoToken切换模型只改 model 字段 [provider.taotoken] type openai-compatible base_url https://taotoken.net/api api_key sk-你的Key粘贴在这里 [model.default] provider taotoken model claude-sonnet-4-20250514 max_tokens 8192 temperature 0.2 [model.fast] provider taotoken model gpt-4o-mini max_tokens 4096 temperature 0.3几个参数说明一下。type 用 openai-compatible 是因为 TaoToken 的接口兼容 OpenAI 的请求格式Trae 能直接识别。temperature 在写代码场景下建议调低0.2 左右比较稳太高的话生成的 SwiftUI 代码容易在细节上飘。max_tokens 给到 8192 是为了让 Trae 一次能吐出完整的 View 文件不然长文件会被截断你还得手动拼接。配置改完记得重启 Trae或者在设置里点一下「重新加载配置」。验证配置是否生效最简单的办法是在对话流里问一句「当前使用的模型是什么」如果返回的模型名和你 config.toml 里写的一致说明通道通了。如果你更习惯用命令行工具做接入调试可以参考接入文档里的示例用 curl 直接打一次请求确认 Key 和地址都没问题再回到 Trae 里操作。文档地址在 https://taotoken.net/doc 。4. SwiftData Model 骨架与 SwiftUI 视图搭建配置通了之后回到项目本身。先在 Xcode 里新建一个 macOS AppInterface 选 SwiftUILanguage 选 Swift勾选 Use SwiftData。项目建好后用 Trae 打开整个文件夹。第一步是定义 Word 模型。在 Trae 对话流里用 #Folder 选中项目根目录把下面这段需求描述贴进去import Foundation import SwiftData Model final class Word { var title: String var isError: Bool var isMaster: Bool init(title: String, isError: Bool false, isMaster: Bool false) { self.title title self.isError isError self.isMaster isMaster } }Model 这个宏是 SwiftData 的核心它会自动帮你生成持久化所需的代码。注意类要用 final 修饰这是 SwiftData 的推荐写法能避免继承带来的元数据问题。三个属性都是存储属性SwiftData 会自动把它们映射到本地数据库的字段。接下来是数据初始化。在 App 的入口文件里注入 ModelContainer并生成 50 条测试数据import SwiftUI import SwiftData main struct WordBookApp: App { var sharedModelContainer: ModelContainer { let schema Schema([Word.self]) let config ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer(for: schema, configurations: [config]) } catch { fatalError(ModelContainer 创建失败: \(error)) } }() var body: some Scene { WindowGroup { ContentView() } .modelContainer(sharedModelContainer) } }这里有个容易踩的坑ModelContainer 的创建如果失败用 fatalError 直接崩掉比静默失败要好因为 SwiftData 的 schema 不匹配时错误信息很隐晦早点暴露问题反而省时间。然后是 ContentView 的三栏结构。侧边栏三个按钮切换右侧列表右侧根据选中状态展示不同过滤条件的数据。核心逻辑用 Query 配合 predicate 实现import SwiftUI import SwiftData enum SidebarItem: String, CaseIterable, Identifiable { case all 单词本 case error 错词 case master 已掌握 var id: String { rawValue } } struct ContentView: View { Environment(\.modelContext) private var context Query private var words: [Word] State private var selection: SidebarItem .all var body: some View { NavigationSplitView { List(SidebarItem.allCases, selection: $selection) { item in Text(item.rawValue).tag(item) } } detail: { switch selection { case .all: WordList(words: words.filter { !$0.isMaster }) case .error: WordList(words: words.filter { $0.isError }) case .master: WordList(words: words.filter { $0.isMaster }) } } .onAppear { if words.isEmpty { generateWords() } } } private func generateWords() { let samples [apple, banana, cherry, dragon, eagle, forest, garden, harbor, island, jungle] for i in 0..50 { let word Word(title: samples[i % samples.count] \(i)) if i 10 { word.isError true } if i 10 i 15 { word.isMaster true } context.insert(word) } try? context.save() } }注意 generateWords 的调用位置。我试过直接写在 body 里编译能过但运行时会报「Modifying state during view update」的警告正确做法是放在 .onAppear 里让它在视图加载完成后再执行。5. 运行验证增删改查是否真的生效代码写完之后按 Cmd R 跑起来。第一次启动时数据库是空的onAppear 会触发 generateWords插入 50 条数据。你应该能看到侧边栏三个选项右侧默认显示单词本列表。验证增删改查分四步走第一步点单词本里任意一条的「添加到错词」按钮然后切到错词标签页看这条数据是否出现在列表里。这一步验证的是 isError 字段的更新和 predicate 过滤是否生效。第二步在错词页点「已掌握」再切到已掌握页确认数据流转正确。这里涉及两个字段同时更新isError 置 falseisMaster 置 true。第三步在已掌握页点「移除」数据应该回到单词本列表。这一步验证的是反向更新。第四步关掉 App 再重新打开数据应该还在。如果重启后数据丢了说明 ModelContainer 的 isStoredInMemoryOnly 被设成了 true检查一下配置。如果某一步没生效先看控制台有没有 SwiftData 的报错。常见的是 predicate 里用了不支持的语法比如对 Bool 字段做复杂比较。SwiftData 的 #Predicate 宏对表达式有限制简单过滤建议在内存里用 filter 做就像上面代码里那样。6. 本篇常见错误排查编译报错「Cannot find Word in scope」检查 Word.swift 文件是否加入了当前 target。在 Xcode 的 File Inspector 里看 Target Membership 有没有勾上。运行时崩溃「ModelContainer 创建失败」多半是 schema 变了但旧数据库还在。删掉 App 的容器目录重新跑路径在 ~/Library/Containers/ 下对应 bundle id 的文件夹里。Trae 生成的代码里 if 和 ! 之间多了空格这是模型输出的格式问题Swift 编译器对if!word.isMaster这种写法会报错。手动改成if !word.isMaster即可或者在 Trae 里追加一句「注意 Swift 语法中 if 和 ! 之间需要空格」。Query 拿到的数据不刷新确认 modelContext 是从 Environment 注入的而不是自己 new 了一个。SwiftData 的上下文必须和 ModelContainer 绑定才能触发视图更新。TaoToken 请求返回 401检查 config.toml 里的 api_key 有没有多余空格以及 base_url 是否写成了带路径的形式。正确写法就是 https://taotoken.net/api 不要在后面加 /v1 之类的后缀。7. 下一步把原型变成日常工具跑通之后你可以在这个骨架上继续加东西。比如给 Word 加一个 createdAt 时间戳字段按添加时间排序或者加一个搜索框用 Query 的动态 predicate 做实时过滤。Trae 在这类增量需求上表现不错用 #File 选中对应文件描述清楚要改什么基本一次就能改对。如果你打算把这个 App 长期用下去建议把模型调用也接进来比如加一个「自动生成例句」的按钮走 TaoToken 的模型对话接口拿返回结果。这样单词本就不只是记录还能帮你扩展用法。模型对话的入口在 https://taotoken.net/models 可以先在网页上试好 prompt 再写进代码。Key 的管理上如果你同时用 Trae 做其他项目建议在 TaoToken 控制台里按项目建不同的 Key方便追踪用量和随时吊销。API Keys 管理页在 https://taotoken.net/api-keys 新建的时候备注一下用途后面排查问题会省事很多。最后提醒一句Trae 生成的代码一定要自己过一遍。它在大结构上很少出错但细节上偶尔会飘比如前面提到的 if 空格问题、onAppear 位置问题。把它当成一个打字很快但需要你 review 的搭档而不是完全放手不管的黑盒这样效率和质量都能兼顾。
返回列表