App Intent 就是一个遵循 AppIntent 的 Swift 类型:有 title,用 @Parameter 声明输入参数,在 perform() 里完成动作并返回 IntentResult。如果输入是你自己的数据(比如一个习惯、一份文档),就用 AppEntity 来描述它,再提供 EntityQuery,让系统能找到具体的实例。想让意图更容易用语音触发、并出现在“快捷指令”App 里,就把它放进 AppShortcutsProvider,写成带口令的 AppShortcut。需要生成样板代码时,可以用我们的 App Intents 与 App Shortcuts 生成器。
写一个意图
Apple 对这个协议的说明是:用本地化字符串实现 title 和 description,每项输入加一个 @Parameter 属性,把动作写在 perform() 里。系统调用 perform() 之前会先解析参数,要么自己推断出值,要么向用户询问。AppIntent 从 iOS 16、macOS 13、watchOS 9、tvOS 16 开始可用。
下面的例子是一个很小的习惯记录。第一段是意图要用到的数据,实体、查询、意图和快捷指令在后面几节。所有代码块拼在一起是一个文件,用 Swift 6.4 对照 iOS 26 SDK 做过类型检查。
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() 返回系统展示选项列表时的初始候选。
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<Value> |
.result(value:dialog:) | 两者都有,见下面的例子 |
.result(opensIntent:) 及其变体 | 请系统打开你的 App 并执行另一个意图 |
.result(dialog:view:) 及其变体 | 附带一个 SwiftUI 视图片段,系统在有屏幕的地方可能会显示它 |
Apple 还提醒:系统不一定会显示你返回的 IntentDialog,比如用户在不看屏幕的情况下使用 Siri,所以别让它成为结果唯一的出口。
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<Int> & 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 的建议是:意图尽量设计成能在后台运行,只有需要交互或更新界面时才加上前台模式。
@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)。同一页还建议口令要简短好记,并提供自然的说法变体。
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,含 supportedModes、openAppWhenRun 和 perform())
- Creating your first app intent
- Adding parameters to an app intent
- IntentModes
- IntentResult、ReturnsValue、ProvidesDialog 与 IntentDialog
- AppEntity、EntityQuery 与 Defining app entities for your custom data types
- App Shortcuts、AppShortcutsProvider、AppShortcut 与 AppShortcutPhraseToken
- App Shortcuts(人机界面指南,口令里的 App 名称)
- App Intents 与 Getting started with the App Intents framework
Apple 的版本和行为可能变化,请以上述页面的最新内容为准。