如何通过Swift AppIntents/Shortcuts+SwiftData实现Siri多字段录入保存记录
问题
尝试配置Shortcuts与AppIntents,实现用户召唤Siri后弹出对话框,创建包含两个及以上SwiftData模型属性的新记录。目前已完成Siri打开应用并跳转至添加记录页面的开发,但无法捕获Siri对话中的用户回复并保存到新记录中。现有代码如下:
@Model class Thing { var name: String = "" var comment: String = "no comment" var recordType: String = "0" var count: Int = 0 init(name: String, comment: String, recordType: String, count: Int) { self.name = name self.comment = comment self.recordType = recordType self.count = count } }//class struct CreateNewThing: AppIntent { static var title: LocalizedStringResource = "Create New Thing" static var description = IntentDescription("Opens the app and moves to the Add Thing screen.") static var openAppWhenRun: Bool = true @AppStorage("navigateToThingAddView") private var navigateToThingAddView: Bool = false @MainActor func perform() async throws -> some IntentResult { navigateToThingAddView = true return .result() } }//create new thing struct ThingAddDialog: AppIntent { static var title: LocalizedStringResource = "Add Thing Dialog" static var description = IntentDescription("Prompt the user for a name and comment for a new Thing.") static var openAppWhenRun: Bool = true @Parameter(title: "Name", default: "") var name: String @Parameter(title: "Comment", default: "No comment") var comment: String @MainActor func perform() async throws -> some ProvidesDialog & ShowsSnippetView { // Logic to navigate to the Add Thing dialog view //navigateToThingDialog = true let dialog = IntentDialog( full: "What is the name of the new Thing?", supporting: "I have opened the Add Thing Dialog.") let answer = $name.needsValueError("Provide a name") name = answer.description saveThing(name: name, comment: comment) return .result(dialog: dialog) } @MainActor private func saveThing(name: String, comment: String) { let newThing = Thing(name: name, comment: comment, recordType: "0", count: 0) // Add newThing to SwiftData let container = try! ModelContainer(for: Thing.self) container.mainContext.insert(newThing) try? container.mainContext.save() } } struct ThingShortcuts: AppShortcutsProvider { static var shortcutTileColor = ShortcutTileColor.navy static var appShortcuts: [AppShortcut] { AppShortcut( intent: CreateNewThing(), phrases: [ "Create a new Thing in \(.applicationName)" ], shortTitle: "Create a Thing", systemImageName: "document.badge.plus" ) AppShortcut( intent: ThingAddDialog(), phrases: ["Add Dialog in \(.applicationName)"], shortTitle: "Add Thing Dialog", systemImageName: "plus.square" ) }//var }//thing shortcuts
使用环境:Xcode 16.1,iOS 18.1
解决方案
核心问题分析
- 参数收集逻辑错误:
$name.needsValueError()仅用于参数缺失时抛出错误提示,无法直接获取用户的语音回复。需要通过Intent参数定义和Siri交互流程收集输入。 - SwiftData容器使用错误:每次调用
saveThing时新建ModelContainer,导致数据无法存入应用主上下文,无法持久化。 - App跳转打断交互:
openAppWhenRun = true会强制打开应用,打断Siri对话流程,若要纯语音交互需关闭该配置。
修改步骤与代码示例
1. 调整Intent参数与交互逻辑
将name设为必填参数,comment设为可选参数,通过Siri自然语言交互收集输入,或通过IntentDialog逐步引导用户提供信息。
2. 正确复用SwiftData容器
使用应用全局的ModelContainer,而非每次新建。
3. 修正保存逻辑
确保参数收集完成后再执行SwiftData的插入与保存操作。
修改后的完整代码:
@Model class Thing { var name: String = "" var comment: String = "no comment" var recordType: String = "0" var count: Int = 0 init(name: String, comment: String, recordType: String, count: Int) { self.name = name self.comment = comment self.recordType = recordType self.count = count } } // 保留原有的跳转页面Intent(若需要) struct CreateNewThing: AppIntent { static var title: LocalizedStringResource = "Create New Thing" static var description = IntentDescription("Opens the app and moves to the Add Thing screen.") static var openAppWhenRun: Bool = true @AppStorage("navigateToThingAddView") private var navigateToThingAddView: Bool = false @MainActor func perform() async throws -> some IntentResult { navigateToThingAddView = true return .result() } } struct ThingAddDialog: AppIntent { static var title: LocalizedStringResource = "Add Thing Dialog" static var description = IntentDescription("Create a new Thing by providing name and comment via Siri.") // 无需打开应用,纯Siri交互 static var openAppWhenRun: Bool = false // 将name设为必填参数,去掉默认值 @Parameter(title: "Name", description: "The name of the new Thing") var name: String @Parameter(title: "Comment", description: "Optional comment for the Thing", default: "No comment") var comment: String @MainActor func perform() async throws -> some IntentResult & ProvidesDialog { // 双重验证name参数 guard !name.isEmpty else { throw $name.needsValueError("Please tell me the name of the new Thing.") } // 保存数据 try saveThing(name: name, comment: comment) // 回复用户操作完成 let dialog = IntentDialog(full: "Created a new Thing named \(name) with comment: \(comment)") return .result(dialog: dialog) } @MainActor private func saveThing(name: String, comment: String) throws { // 获取应用全局的ModelContainer guard let container = ModelContainer.shared as? ModelContainer<Thing> else { fatalError("ModelContainer not initialized") } let newThing = Thing(name: name, comment: comment, recordType: "0", count: 0) container.mainContext.insert(newThing) try container.mainContext.save() } } struct ThingShortcuts: AppShortcutsProvider { static var shortcutTileColor = ShortcutTileColor.navy static var appShortcuts: [AppShortcut] { AppShortcut( intent: CreateNewThing(), phrases: ["Create a new Thing in \(.applicationName)"], shortTitle: "Create a Thing", systemImageName: "document.badge.plus" ) AppShortcut( intent: ThingAddDialog(), phrases: [ "Add a Thing named \(\.$name) with comment \(\.$comment) in \(.applicationName)", "Create a Thing via Siri in \(.applicationName)" ], shortTitle: "Add Thing via Siri", systemImageName: "plus.square" ) } }
额外配置说明
- App初始化SwiftData容器:需在App入口处初始化全局容器,例如:
@main struct YourApp: App { let container: ModelContainer init() { do { container = try ModelContainer(for: Thing.self) // 设置全局共享容器 ModelContainer.shared = container } catch { fatalError("Failed to initialize ModelContainer: \(error)") } } var body: some Scene { WindowGroup { ContentView() .modelContainer(container) } } }
- Siri短语优化:为
ThingAddDialog添加灵活的语音短语,支持直接说出名称和评论,Siri会自动解析参数。
内容的提问来源于stack exchange,提问作者JohnSF
相关产品推荐
相关产品推荐

