# Swift 的 App Intents 与 App Shortcuts 指南 > 怎么写带参数的 AppIntent、用 AppEntity 和 EntityQuery 描述数据、返回对话或数值、选择 supportedModes,以及怎么写 App Shortcuts 的口令。 - Canonical page: https://obelisk.club/blog/app-intents-shortcuts-guide-zh - Author: Peter Zhang - Published: 2026-11-01 - Tags: app-intents, shortcuts, siri, swift, ios, indie, 中文 App Intent 就是一个遵循 `AppIntent` 的 Swift 类型:有 `title`,用 `@Parameter` 声明输入参数,在 `perform()` 里完成动作并返回 `IntentResult`。如果输入是你自己的数据(比如一个习惯、一份文档),就用 `AppEntity` 来描述它,再提供 `EntityQuery`,让系统能找到具体的实例。想让意图更容易用语音触发、并出现在“快捷指令”App 里,就把它放进 `AppShortcutsProvider`,写成带口令的 `AppShortcut`。需要生成样板代码时,可以用我们的 [App Intents 与 App Shortcuts 生成器](/tools/app-intents-shortcuts-builder)。 ## 写一个意图 Apple 对这个协议的说明是:用本地化字符串实现 `title` 和 `description`,每项输入加一个 `@Parameter` 属性,把动作写在 `perform()` 里。系统调用 `perform()` 之前会先解析参数,要么自己推断出值,要么向用户询问。`AppIntent` 从 iOS 16、macOS 13、watchOS 9、tvOS 16 开始可用。 下面的例子是一个很小的习惯记录。第一段是意图要用到的数据,实体、查询、意图和快捷指令在后面几节。所有代码块拼在一起是一个文件,用 Swift 6.4 对照 iOS 26 SDK 做过类型检查。 ```swift import AppIntents struct Habit: Identifiable, Sendable { let id: UUID var name: String var count: Int } actor HabitStore { static let shared = HabitStore() private var habits = [ Habit(id: UUID(), name: "Water", count: 0), Habit(id: UUID(), name: "Stretch", count: 0), ] func all() -> [Habit] { habits } func habits(for ids: [UUID]) -> [Habit] { habits.filter { ids.contains($0.id) } } func increment(_ id: UUID, by amount: Int) -> Int { guard let index = habits.firstIndex(where: { $0.id == id }) else { return 0 } habits[index].count += amount return habits[index].count } } ``` ## 用 AppEntity 和 EntityQuery 描述数据 `AppEntity` 让自定义类型可以作为意图参数。Apple 的说明有几点:实体必须有唯一标识符(系统可能把它存进快捷指令里);`id` 用 `UUID`、`String` 或 `Int` 最简单;通常更好的做法是专门为意图建一个小的实体类型,而不是直接让主数据模型遵循这个协议。想让系统看到的属性加 `@Property`,再提供 `displayRepresentation` 和静态的 `defaultQuery`。一个 `AppEntity` 实例连同子属性的总大小上限是 10 MB。 `EntityQuery` 是系统查找实体的入口。`entities(for:)` 接收一组标识符,返回对应的实例;Apple 说如果某个实体已经不存在,就从返回数组里省略。`suggestedEntities()` 返回系统展示选项列表时的初始候选。 ```swift struct HabitEntity: AppEntity { static let typeDisplayRepresentation: TypeDisplayRepresentation = "Habit" static let defaultQuery = HabitQuery() let id: UUID @Property(title: "Name") var name: String var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(name)") } init(_ habit: Habit) { self.id = habit.id self.name = habit.name } } struct HabitQuery: EntityQuery { func entities(for identifiers: [UUID]) async throws -> [HabitEntity] { await HabitStore.shared.habits(for: identifiers).map(HabitEntity.init) } func suggestedEntities() async throws -> [HabitEntity] { await HabitStore.shared.all().map(HabitEntity.init) } } ``` ## 添加参数并返回结果 Apple 的参数指南说:非可选属性就是必填参数,缺值时系统会去询问;可选属性则被当作可选参数。支持的参数类型包括 `Bool`、`Int`、`Double`、`String`、`Date`、`Duration`、`Measurement`、`URL`、这些类型的集合,以及你自己的 `AppEntity` 和 `AppEnum`。`parameterSummary` 用一句话在快捷指令编辑器里描述动作和它的参数。 `perform()` 返回 `some IntentResult`。Apple 建议把 `ReturnsValue`、`ProvidesDialog`、`OpensIntent`、`ShowsSnippetView` 这些结果类型,和对应的 `.result(...)` 方法组合使用。 | 返回什么 | 用在哪里 | |---|---| | `.result()` | 动作完成,没有什么要说的 | | `.result(dialog:)` | 系统可以显示或朗读的文字(`IntentDialog`) | | `.result(value:)` | 可以交给下一个动作的数值,即 `ReturnsValue` | | `.result(value:dialog:)` | 两者都有,见下面的例子 | | `.result(opensIntent:)` 及其变体 | 请系统打开你的 App 并执行另一个意图 | | `.result(dialog:view:)` 及其变体 | 附带一个 SwiftUI 视图片段,系统在有屏幕的地方可能会显示它 | Apple 还提醒:系统不一定会显示你返回的 `IntentDialog`,比如用户在不看屏幕的情况下使用 Siri,所以别让它成为结果唯一的出口。 ```swift struct LogHabitIntent: AppIntent { static let title: LocalizedStringResource = "Log Habit" static let description = IntentDescription("Add to today's count for a habit.") @Parameter(title: "Habit") var habit: HabitEntity @Parameter(title: "Amount", default: 1) var amount: Int static var parameterSummary: some ParameterSummary { Summary("Log \(\.$amount) for \(\.$habit)") } func perform() async throws -> some IntentResult & ReturnsValue & ProvidesDialog { let total = await HabitStore.shared.increment(habit.id, by: amount) return .result(value: total, dialog: "\(habit.name) is at \(total) today.") } } ``` ## 用 supportedModes 打开 App `openAppWhenRun` 是以前的做法。Apple 的页面现在把它标为已弃用,要求改用 `supportedModes`;页面还说,如果意图运行在 App 扩展里,把 `openAppWhenRun` 设为 `true` 会产生错误。`supportedModes` 和 `IntentModes` 从 iOS、iPadOS、macOS、tvOS、visionOS、watchOS 的 26 版本开始可用,所以还要支持更早系统的话,记得加 `@available`。 Apple 列出的 `supportedModes` 取值: | 取值 | 作用 | |---|---| | `.background` | 动作完全在后台运行 | | `.foreground(.immediate)` | 动作开始前先把 App 带到前台 | | `.foreground(.dynamic)` | 在后台运行,可以按需转到前台 | | `.foreground(.deferred)` | 在后台运行,动作完成前再转到前台 | | `[.background, .foreground(.deferred)]` | 在后台开始,动作结束前转到前台 | 这些值可以像选项集一样组合:`.foreground` 加 `.background` 表示尽量在前台运行、必要时允许后台;`.background` 加 `.foreground(.dynamic)` 表示前后台都可以,但偏向后台。在 `perform()` 里,可以通过 `systemContext` 得知当前的模式,以及能否转到前台;`continueInForeground(_:alwaysConfirm:)` 用来请求转到前台,如果做不到,系统会抛出 `notAllowed` 错误。Apple 的建议是:意图尽量设计成能在后台运行,只有需要交互或更新界面时才加上前台模式。 ```swift @available(iOS 26.0, *) struct OpenHabitsIntent: AppIntent { static let title: LocalizedStringResource = "Open Habits" static let supportedModes: IntentModes = .foreground func perform() async throws -> some IntentResult { .result() } } ``` ## 用 AppShortcutsProvider 提供 App Shortcuts App Shortcut 把一个意图和短标题、图标、口令放在一起。你在遵循 `AppShortcutsProvider`(iOS 16 起)的类型里,通过 `appShortcuts` 声明它们。Apple 说编译器会生成系统需要的信息,所以 App 一安装快捷指令就可用,不需要自己去注册。 每条口令是一个 `AppShortcutPhrase`。Apple 的人机界面指南说,口令里要包含你的 App 名称,写法就是 `\(.applicationName)` 这个占位(对应 `AppShortcutPhraseToken.applicationName`)。同一页还建议口令要简短好记,并提供自然的说法变体。 ```swift struct HabitShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut( intent: LogHabitIntent(), phrases: [ "Log a habit in \(.applicationName)", "Add to my habits in \(.applicationName)", ], shortTitle: "Log Habit", systemImageName: "checkmark.circle" ) } } ``` Apple 还提供了 `SiriTipView` 和 `SiriTipUIView`,可以在 App 里展示某个意图的口令;`ShortcutsLink` 则可以打开你的 App 在“快捷指令”里的页面。Apple 建议意图、App Shortcuts 和参数都使用本地化字符串。 ## 意图会出现在哪里 Apple 的 App Intents 概览列出了这个框架能触达的地方: - Siri:用户可以用语音使用你的动作和内容。 - Spotlight:从搜索结果直接带用户进入你的数据;用 Core Spotlight 索引实体会有帮助。 - “快捷指令”App:用户在里面搭建包含你动作的工作流,App Shortcuts 也出现在这里。 - iPhone 的操作按钮和 Apple Pencil:可以设置为执行你的动作。 - 小组件、控制项和实时活动:用 App Intent 实现可交互的按钮和开关。 - 专注模式:可以定义自定义的专注模式,并响应专注模式的变化。 Apple 的框架指南还提到:很多意图可以安静地在后台运行;可以用 App 扩展来运行其中一部分,这样 App 不必启动;如果一个意图前台后台都能跑,它的代码可以放进由 App 和扩展共用的 Swift 包里。 ## 来源 以下页面均阅读于 2026 年 11 月 1 日。 - [AppIntent](https://developer.apple.com/documentation/appintents/appintent),含 [supportedModes](https://developer.apple.com/documentation/appintents/appintent/supportedmodes)、[openAppWhenRun](https://developer.apple.com/documentation/appintents/appintent/openappwhenrun) 和 [perform()](https://developer.apple.com/documentation/appintents/appintent/perform()) - [Creating your first app intent](https://developer.apple.com/documentation/appintents/creating-your-first-app-intent) - [Adding parameters to an app intent](https://developer.apple.com/documentation/appintents/adding-parameters-to-an-app-intent) - [IntentModes](https://developer.apple.com/documentation/appintents/intentmodes) - [IntentResult](https://developer.apple.com/documentation/appintents/intentresult)、[ReturnsValue](https://developer.apple.com/documentation/appintents/returnsvalue)、[ProvidesDialog](https://developer.apple.com/documentation/appintents/providesdialog) 与 [IntentDialog](https://developer.apple.com/documentation/appintents/intentdialog) - [AppEntity](https://developer.apple.com/documentation/appintents/appentity)、[EntityQuery](https://developer.apple.com/documentation/appintents/entityquery) 与 [Defining app entities for your custom data types](https://developer.apple.com/documentation/appintents/defining-app-entities-for-your-custom-data-types) - [App Shortcuts](https://developer.apple.com/documentation/appintents/app-shortcuts)、[AppShortcutsProvider](https://developer.apple.com/documentation/appintents/appshortcutsprovider)、[AppShortcut](https://developer.apple.com/documentation/appintents/appshortcut) 与 [AppShortcutPhraseToken](https://developer.apple.com/documentation/appintents/appshortcutphrasetoken) - [App Shortcuts](https://developer.apple.com/design/human-interface-guidelines/app-shortcuts)(人机界面指南,口令里的 App 名称) - [App Intents](https://developer.apple.com/documentation/appintents) 与 [Getting started with the App Intents framework](https://developer.apple.com/documentation/appintents/getting-started-with-the-app-intents-framework) *Apple 的版本和行为可能变化,请以上述页面的最新内容为准。*