Highlight
这场 Session 是 App Intents 框架的从零入门,从最基础的 intent、entity、query 三个核心概念讲起,一路延伸到 App Shortcut、Spotlight 索引、SwiftUI 导航集成等进阶用法。主讲人用一个地标浏览 app 做贯穿始终的 demo,逐步构建出一个能在 Spotlight、Siri、Action Button 中被调用的完整意图体系。
核心内容
很多 app 都有这样的痛点:用户想跳到某个具体的页面,必须先打开 app、再点几层菜单。Siri 喊一声、Spotlight 搜一下、Action Button 按一下就直达,听起来很美好,但中间隔着一整套 app 之外的系统能力。App Intents 就是 Apple 拿来填这条缝的框架。
主讲人 James 用一个地标 app 做演示。app 有三个 tab:Landmarks 列表、Map、Collections。他先写了 20 行代码,把”跳转到 Landmarks 页”包成 NavigateIntent。装上 app 之后,Shortcuts 里立刻能看到这个动作;再加一行 static let supportedModes: IntentModes = .foreground,运行时 app 会自动被拉到前台。然后他把跳转目标抽成 NavigationOption 这个 AppEnum,配合 @Parameter 让用户选择目的地,再补一个 AppShortcutsProvider,整套能力就在 Siri、Spotlight、Action Button 上同时点亮。
整个框架的设计哲学是把 app 的功能抽象成”动词”(intent)和”名词”(entity),然后通过 query 让系统理解你的数据。今年的更新集中在四件事:entity 属性支持 @ComputedProperty,从已有 model 延迟取值不必再拷贝;Spotlight 支持在 @Property 上直接标注 indexingKey;新增 TargetContentProvidingIntent 协议配合 onAppIntentExecution modifier 实现声明式导航;以及 App Intents 终于支持 Swift Package 和 static library 中定义类型,靠 AppIntentsPackage 注册跨 target 共享。
详细内容
App Intents 的最小单元是一个实现 AppIntent 协议的 struct,要求只有两件:一个 title、一个 perform()。下面是 demo 里最初的 NavigateIntent(03:23):
struct NavigateIntent: AppIntent {
static let title: LocalizedStringResource = "Navigate to Landmarks"
static let supportedModes: IntentModes = .foreground
@MainActor
func perform() async throws -> some IntentResult {
Navigator.shared.navigate(to: .landmarks)
return .result()
}
}
关键点:
title必须是常量字符串,因为框架在编译期读取源码生成元数据,不能用计算属性。supportedModes = .foreground让 intent 执行前先把 app 拉到前台;默认值是后台执行。@MainActor保证 perform 在主线程跑,因为 navigation 必须在主线程上。- 返回
IntentResult可以携带 dialog(Siri 朗读)、view snippet(系统弹窗中渲染)和 ReturnsValue(作为下一步 intent 的输入)。
光会跳一个固定页面太死板。第二步是把目标变成参数(05:38):
struct NavigateIntent: AppIntent {
static let title: LocalizedStringResource = "Navigate to Section"
static let supportedModes: IntentModes = .foreground
static var parameterSummary: some ParameterSummary {
Summary("Navigate to \(\.$navigationOption)")
}
@Parameter(
title: "Section",
requestValueDialog: "Which section?"
)
var navigationOption: NavigationOption
@MainActor
func perform() async throws -> some IntentResult {
Navigator.shared.navigate(to: navigationOption)
return .result()
}
}
关键点:
@Parameter把变量声明为 intent 输入;不加Optional表示必填,运行时会先要求用户提供值再调用 perform。parameterSummary用Summary("Navigate to \(\.$navigationOption)")把动作和参数串成一句自然语言,Shortcuts 会把参数渲染成可点的 inline 控件。requestValueDialog是 Siri 询问用户时朗读的提示语。- 今年新规则:只要 intent 实现了完整的 parameter summary,就能在 macOS Spotlight 中直接运行(08:09)。
接下来要把 app 里真正动态的”名词”建模出来。Landmark 数量不固定,必须用 AppEntity。其中今年新增的 @ComputedProperty 让你不必把数据从 model 拷贝到 entity(11:02):
struct LandmarkEntity: AppEntity {
var id: Int { landmark.id }
@ComputedProperty
var name: String { landmark.name }
@ComputedProperty
var description: String { landmark.description }
let landmark: Landmark
static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Landmark")
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)")
}
static let defaultQuery = LandmarkEntityQuery()
}
关键点:
id必须是持久化的、可用于数据库查找的标识符;系统会缓存这个 id,重启后仍能解析回原始 entity。@ComputedProperty是今年新加的,等价于 getter,Shortcuts 中读到这些字段时才去访问 model,避免重复存储。defaultQuery把 entity 与EntityQuery关联,系统通过这个 query 回答”这个 id 对应哪个 entity”等问题。
Query 是 entity 的”门牌号”。最基础的实现负责回答”What is the entity for this ID”(13:19):
struct LandmarkEntityQuery: EntityQuery {
@Dependency var modelData: ModelData
func entities(for identifiers: [LandmarkEntity.ID]) async throws -> [LandmarkEntity] {
modelData
.landmarks(for: identifiers)
.map(LandmarkEntity.init)
}
}
关键点:
@Dependency把外部数据源注入 query;要在 app 启动早期通过AppDependencyManager.shared.add { ModelData() }注册。entities(for:)是所有 query 必须实现的方法,系统拿到 id 列表后通过它解析回 entity 实例。- 在此之上还可以扩展
EnumerableEntityQuery(返回全部)、EntityPropertyQuery(带谓词排序)、EntityStringQuery(按字符串匹配)三种查询协议,对应 Shortcuts 中的 Find、Filter、Search 行为。
最后一块是导航的声明式写法。今年新增的 TargetContentProvidingIntent 不再要求 perform 方法,导航逻辑直接挂在 SwiftUI view 上(18:17):
struct OpenLandmarkIntent: OpenIntent, TargetContentProvidingIntent {
static let title: LocalizedStringResource = "Open Landmark"
@Parameter(title: "Landmark", requestValueDialog: "Which landmark?")
var target: LandmarkEntity
}
struct LandmarksNavigationStack: View {
@State var path: [Landmark] = []
var body: some View {
NavigationStack(path: $path) {}
.onAppIntentExecution(OpenLandmarkIntent.self) { intent in
path.append(intent.target.landmark)
}
}
}
关键点:
OpenIntent协议要求一个名为target的参数,并自动在 perform 之前把 app 拉到前台。TargetContentProvidingIntent让你跳过perform实现,把”拿到参数后做什么”挪到 view 层。onAppIntentExecution(...)modifier 监听匹配的 intent,并在闭包里直接修改 SwiftUI navigation path,避免之前那种全局 Navigator 单例的胶水代码。
核心启发
-
做什么:给现有 app 加一个最小化的
AppShortcut,把”打开常用页面”暴露给 Spotlight 和 Siri。- 为什么值得做:从 Session 给的成本看,这就是几十行 Swift;但用户体验立即升级——打开 app 不必先点首页再点 tab,Action Button 也能直接绑定。
- 怎么开始:参照 03:23 的
NavigateIntent写一个无参数版本,再在AppShortcutsProvider里挂一个短语,安装后立刻在 Shortcuts 里看到效果。
-
做什么:把 app 里频繁出现的”领域对象”(订单、笔记、收藏夹、地点)建成
AppEntity并实现IndexedEntity。- 为什么值得做:实现
IndexedEntity后系统会代你把 entity 写进 Spotlight 索引,并支持语义搜索;用户在锁屏下拉就能搜到你的内容,竞争力直接拉到系统级。 - 怎么开始:先用
@ComputedProperty包裹现有 model 字段(避免数据双写),再给关键属性加@Property(indexingKey:),最后调用CSSearchableIndex的 donate 方法。
- 为什么值得做:实现
-
做什么:把 app 内部的导航逻辑从全局单例迁移到
TargetContentProvidingIntent+onAppIntentExecution。- 为什么值得做:这种声明式写法让 deep link、Spotlight 点击、Siri 语音三条入口共享同一份导航代码,省掉手写 router;同时 SwiftUI 的状态可观察性也保留下来。
- 怎么开始:先挑一个最常用的”打开详情页”场景,定义
OpenXxxIntent实现两个协议;在 NavigationStack 上挂onAppIntentExecution,闭包里 append path。
-
做什么:给 entity 实现
Transferable,把图片、文档等代表性内容暴露给 Shortcuts 和其他 app。- 为什么值得做:用户能把你的 entity 直接喂进系统相册、消息、Mail 等动作里,多步 Shortcut 可以自动串起来;这是免费拿到的跨 app 互通能力。
- 怎么开始:参考 16:33 的
DataRepresentation(exportedContentType: .image),把 entity 内部已有的图像数据声明出来即可。
-
做什么:把 App Intents 类型放进 Swift Package,配合
AppIntentsPackage协议跨 target 共享。- 为什么值得做:今年起 package 和 static library 都能定义 intent,方便把核心域模型和 UI 解耦;多 target 项目(主 app + extension + widget)可以共用一套 entity,避免重复声明。
- 怎么开始:把 entity 文件搬到 SPM target,定义
struct XxxKitPackage: AppIntentsPackage {};在 app target 的 package 里通过includedPackages引入它。
关联 Session
- Design interactive snippets — App Intent 配套的 snippet view 设计指南,介绍怎样让 perform 结果在系统中渲染得有用、好看。
- Code-along: Bring on-device AI to your app using the Foundation Models framework — 把端侧大模型接入 SwiftUI app 的代码实战,App Intent 是它的常见调用入口。
- Deep dive into the Foundation Models framework — Foundation Models 的进阶用法,guided generation 和工具调用与 App Intent 概念互通。
- Bring advanced speech-to-text to your app with SpeechAnalyzer — 新的 SpeechAnalyzer API,常被组合进 App Intent 实现语音到动作的链路。
评论
GitHub Issues · utterances