Highlight
App Intents 是 Siri、Spotlight、Shortcuts、Widget、Control Center 等所有系统集成的统一协议层——你定义一次 Intent、Entity、Query,就能同时驱动多个系统入口。
核心内容
你的 App 做得再好,用户不打开它就什么都看不到。Christopher Nebel 在开场用了一个很直白的比喻:每个 App 都是一个精心打造的”盒子”,盒子里体验完美,但用户在盒子之间切换本身就是一种摩擦(friction)。切换 App 需要时间和注意力,这种小摩擦反复出现,就会打断用户的操作流(flow)。
App Intents 要解决的就是这个问题。它让系统能理解你 App 里的核心动作和内容对象,然后直接把它们展示在 Spotlight 搜索建议、Siri 对话、Home Screen Widget、Control Center 控件、Action Button、Apple Pencil Pro 挤压等位置。用户不需要先打开你的 App,就能完成关键操作。Session 全程用一个徒步路线 App 做演示:从定义最简单的 OpenPinnedTrailIntent 开始,逐步扩展到带参数的 Intent、可配置 Widget、Control Center 按钮、App Shortcut,展示了同一套 Entity 和 Query 如何在不同功能之间复用。
关键洞察在于代码复用。你为 OpenTrail Intent 定义的 TrailEntity 和 TrailEntityQuery,在 Widget 的配置参数、Control Center 的按钮配置、Siri 的自然语言交互中都能直接使用。写一次,到处生效。
详细内容
Intent:定义动作(09:31)
一个 Intent 是遵循 AppIntent 协议的类型,必须提供两个东西:title(本地化标题,在 Shortcuts 中显示为动作名称)和 perform() 方法(执行实际操作)。
struct OpenPinnedTrailIntent: AppIntent {
static var title: LocalizedStringResource = "Open Pinned Trail"
static var openAppWhenRun = true
func perform() throws -> some IntentResult {
// 导航到已置顶的路线详情页
TrailNavigator.navigateToPinnedTrail()
return .result()
}
}
关键点:
title是本地化字符串,Shortcuts App 中直接用它作为动作名openAppWhenRun = true告诉系统执行时需要打开 App 前台perform()返回IntentResult,这里结果为空(.result()),因为只需要跳转页面- Intent 命名应该是用户可理解的动作(OpenPinnedTrail),不要用内部实现名称
Entity + Query:定义内容和查找逻辑(12:13)
Entity 是 App 里的”名词”——核心内容对象。它遵循 AppEntity 协议,必须提供 displayRepresentation(展示用)、id(持久化标识符)和 query(查询对象)。
struct TrailEntity: AppEntity {
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Trail")
static var defaultQuery = TrailEntityQuery()
var id: UUID
var name: String
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)")
}
}
Query 负责回答两类问题:有哪些 Entity 可选?某个 ID 对应哪个 Entity?最简单的方式是遵循 EnumerableEntityQuery,提供 allEntities() 方法:
struct TrailEntityQuery: EntityQuery, EnumerableEntityQuery {
func allEntities() async throws -> [TrailEntity] {
// 返回所有路线,适用于数据量能全部载入内存的情况
TrailStore.shared.allTrails.map { trail in
TrailEntity(id: trail.id, name: trail.name)
}
}
func entities(for identifiers: [UUID]) async throws -> [TrailEntity] {
// 根据 ID 查找,用于 Intent 执行时还原参数
allEntities().filter { identifiers.contains($0.id) }
}
}
关键点:
- 用 iOS 18 SDK 构建时,
EnumerableEntityQuery可以自动派生出搜索、谓词查询等更复杂的查询协议(14:18) - 如果数据量太大无法全部载入内存,应改用
EntityStringQuery等其他子协议 - Query 的
entities(for:)方法让系统在运行 Intent 时能把保存的 ID 还原为完整的 Entity 对象,你的 Intent 代码拿到的是 Entity 而非裸 ID
带参数的 Intent:OpenIntent(11:04)
当 Intent 需要接收用户选择的内容对象时,使用 OpenIntent 协议:
struct OpenTrailIntent: OpenIntent {
static var title: LocalizedStringResource = "Open Trail"
@Parameter(title: "Trail")
var target: TrailEntity
func perform() throws -> some IntentResult {
TrailNavigator.navigateToTrail(id: target.id)
return .result()
}
}
关键点:
OpenIntent隐含openAppWhenRun,不需要再手动声明@Parameter标记的属性会自动出现在 Shortcuts 的参数选择界面,Query 提供候选项- 参数类型应该是 Entity 本身(
TrailEntity),而不是描述 Entity 的数据(如 trail name 或 UUID)
Parameter Summary:让 Shortcuts 显示可读的操作描述(15:00)
static var parameterSummary: some ParameterSummary {
Summary("Open \.$target")
}
关键点:
- 没有 parameterSummary 时,Shortcuts 中参数会折叠在列表下方,难以阅读
- 加上后,动作会显示为类似”Open Monterey Bay Coastal Trail”的自然语言句子
- 用户编辑参数时,占位符会实时替换,让操作意图一目了然
Widget 与 Control Center:复用已有 Entity(16:59)
Widget 的配置 Intent 遵循 WidgetConfigurationIntent,可以直接复用之前定义的 TrailEntity 作为参数类型。Control Center 的可配置控件同理,使用 ControlConfigurationIntent 协议:
extension OpenTrailIntent: ControlConfigurationIntent {}
// 空扩展——OpenTrailIntent 已具备所需的一切
关键点:
- 一个 Intent 可以同时遵循多个配置协议,不需要为 Widget 和 Control Center 分别写不同的 Intent
- Control Center 按钮的点击动作可以直接传配置好的 Intent 实例,因为它已经有
perform()方法和参数值
App Shortcut:让功能自动出现在 Spotlight 和 Siri(20:40)
struct TrailAppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: OpenPinnedTrailIntent(),
phrases: [
"Open my pinned trail in \(.applicationName)"
],
shortTitle: "Open Pinned Trail",
systemImageName: "pin.fill"
)
}
}
关键点:
- App Shortcut 包装的是 Intent 实例而非类型,所以可以在注册时预填参数
phrases中必须包含\.applicationName,Siri 需要它来匹配用户的语音指令- 不需要手动注册代码——框架自动检测
AppShortcutsProvider并处理注册,App 安装后即可用 - 已有的 App Shortcut 在今年自动支持 Apple Pencil Pro 挤压,去年也自动支持了 Action Button(22:30)
返回 Dialog + Snippet 而非打开 App(22:56)
当 Siri 执行 Intent 时,如果只是展示信息而非操作页面,可以返回包含 Dialog 和 SwiftUI 视图的结果,避免跳转 App:
struct ShowPinnedTrailIntent: AppIntent {
static var title: LocalizedStringResource = "Show Pinned Trail Conditions"
func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView {
let trail = TrailStore.shared.pinnedTrail
return .result(
dialog: "\(trail.name) conditions: \(trail.conditions)",
view: TrailConditionsView(trail: trail)
)
}
}
关键点:
ProvidesDialog让 Siri 可以朗读文本,ShowsSnippetView让 Siri 在屏幕上展示 SwiftUI 片段- Dialog 和视图文本可以不同:朗读版本和视觉版本各有侧重
- 视图使用 Widget 同款的归档机制,只能用 Widget 支持的 SwiftUI 特性
- 用户看完 Snippet 后停留在原位,不会被弹到你的 App 里
核心启发
-
做什么:为核心内容模型定义
AppEntity+EntityQuery。为什么值得做:Entity 是所有 App Intents 功能的基石,定义一次后 Shortcuts 参数选择、Widget 配置、Control Center 配置、Siri 理解自然语言全部自动获得支持,后续扩展零成本。怎么开始:挑出 App 中最核心的 1-2 个数据模型,让它们遵循AppEntity协议,实现displayRepresentation、id和EnumerableEntityQuery。 -
做什么:把最高频操作做成 App Shortcut。为什么值得做:App Shortcut 安装即生效,自动出现在 Spotlight 建议和 Siri 中,用户无需任何配置就能触达你的核心功能。这是投入产出比最高的集成方式。怎么开始:找到用户最常执行的 1-2 个操作(比如”打开最近文档”、“查看收藏”),用
AppShortcutsProvider包装成 App Shortcut,写好 phrases 和 shortTitle。 -
做什么:用同一个 Intent 驱动多个系统入口。为什么值得做:Session 演示中
OpenTrailIntent同时作为 Shortcuts 动作、Widget 配置、Control Center 按钮和 Siri 指令的底层实现,代码只写一次。一个 Intent 遵循多个协议(OpenIntent+WidgetConfigurationIntent+ControlConfigurationIntent)就能覆盖所有场景。怎么开始:检查现有的 SiriKit Intent 或自定义 URL Scheme 处理逻辑,把它们统一迁移为 App Intent,然后用协议扩展逐步接入 Widget 和 Control Center。
关联 Session
- Designing App Intents — 讲解如何设计出在 Siri、Shortcuts、Spotlight 等各处都表现良好的 App Intent
- Bring your app to Siri — 深入介绍 App Intents 与 Siri 的集成细节
- What’s new in App Intents — 2024 年 App Intents 框架的最新更新
- Explore enhancements to App Intents — WWDC23 关于 Widget 配置和 Entity Query 增强的详解
评论
GitHub Issues · utterances