-
跟随编程:让你的 App 支持 Siri
跟随 Xcode 项目演示,深入探索如何让你的 App 支持 Siri。了解如何采用 App Schemas,让用户能够询问有关日历日程的问题,并执行日程安排等自然语言操作。探索相关最佳做法,以便将内容纳入“聚焦”语义索引,并为屏幕感知提供情境信息。
章节
- 0:00 - Introduction
- 1:43 - App Schemas and the plan
- 3:44 - Build the CalendarEntity
- 8:00 - Build the AttendeeEntity
- 10:30 - Build the EventEntity
- 14:34 - Open events with OpenIntent
- 15:30 - Onscreen awareness
- 17:18 - Create events with Siri
- 19:24 - Update events
- 21:30 - Custom snippet views
- 22:30 - Delete events
- 23:35 - Next steps
资源
- Integrating your calendar app with Apple Intelligence
- Donating your app’s data and actions to the system
- Donations and discovery
- Making app entities available in Spotlight
- Making actions and content discoverable by Apple Intelligence
- Providing contextual cues to Apple Intelligence and Siri
- Apple Intelligence and Siri AI
- Calendar
- App schema domains
相关视频
WWDC26
-
搜索此视频…
嗨 我是Justin 是Swift 智能框架团队的工程师 欢迎来到这个代码实践 在这个视频中 我将逐步 让一个App接入Siri 想象一下 我在iPhone上 和Siri开始对话并问道 「谁会来参加我的野餐?」 Siri搜索了该活动及其与会者 并向我显示了宾客名单
我发现其中有一个总是迟到的朋友 于是我说 「把它改到中午…… 实际上是上午11:30」
Siri要求确认详情 然后我说 「好的 谢谢」
Siri做出更改并呈现 一个自定义视图
现在我需要通知我的朋友们 「给所有参加野餐的人发短信 用表情符号告知时间更改」
在仔细确认消息后 我说 「发送」
就这样 Siri发送了消息 然后我想起活动备注中 有一个提醒 提示要带一样重要的东西 去野餐 于是我问 「我参加野餐需要 带什么来着?」 Siri搜索活动 发现了 我的备注 要带巧克力蛋糕
我很好奇还有多少时间 可以准备 于是我说 「开车去那里需要多长时间?」
Siri找到了活动位置 并给出了估算
这就是Siri让日常事务 变得轻松的方式 只需对话 这就是今天的目标 我将使用一个现有App 让它接入Siri Siri由Apple 智能驱动 这是Apple的个人智能系统 开发者将他们的App 接入Apple 智能 通过App Intents框架 让App的内容和操作 在Siri中可用 以及其他系统体验 配套视频 「Build intelligent Siri experiences with App Schemas」 涵盖了框架背后的概念 以及Apple 智能和Siri如何 与App内容和操作协作 如果你对App Intents还不熟悉 视频「Get to know App Intents」 涵盖了核心基础知识 如intents entities和queries 以及它们如何连接Siri 和Shortcuts等系统功能 我一直在开发一个名为 CometCal的示例项目 这是一个SwiftUI日历App 带有有趣的宇宙主题 源代码可在Apple开发者网站上 下载 供你跟随学习 它涵盖了日历App的所有基础功能 如显示今天的活动 查看和编辑活动 创建更多
甚至管理不同的日历
目前 与它交互的唯一方式 是通过屏幕…… 但这即将改变! 在这个视频中 我将更新CometCal 让Siri能够理解App内容 并回答我的问题 并执行操作 如更新活动—— 让Siri比以往更有用 第一步是让Siri 理解我的App内容 目前Siri不知道 CometCal中的日历 或活动在CometCal中意味着什么 这就是App Schemas的用武之地 App Schemas描述了我的App内容 和操作 以Siri已能理解的方式 它们定义了我的实体结构 和操作参数 以及输出结果 无需训练短语 无需自然语言处理 App Schemas被组织成 App Schema Domains
日历域涵盖了所有与 日程安排相关的内容 活动 日历 与会者 以及对其进行操作的动作 基础工作已经完成…… 准备好 跳入Xcode 开始倒计时! CometCal的数据层 已有CalendarModel 这是用于日历的SwiftData模型 如Personal日历与 Work日历之间的区别 目标是使用App Schemas 创建一个代表它的App实体 让Siri了解该App中 日历是什么 我将创建一个名为CalendarEntity 的Swift文件 并导入AppIntents
接下来 我将在编辑器中 输入calendar_ Xcode在自动补全中提供了 Calendar域中的每个Schema 由于目标是日历实体 我将选择calendar_calendar
代码片段填充了结构 @AppEntity宏 属性 DisplayRepresentation和查询存根 这现在是一个Schema化实体 Siri可以对其进行推理的类型 还有几处需要填写 首先 我将id类型设为UUID 以匹配数据模型
为了启用基于语义 而非仅文本的匹配 我将遵循IndexedEntity协议 遵循IndexedEntity 允许我的App 通过Spotlight索引捐献实体 以获得语义理解的优势 实体被捐献后 Siri可以 通过名称解析它 通过属性或上下文解析 无需自定义属性查询 为了加快进度 我添加了 在两者之间转换的方法 在数据模型和实体之间 CalendarEntity的初始化器 从CalendarModel映射 提取实体所需的内容 CalendarModel上的 便捷.entity属性 使用初始化器生成CalendarEntity 我很快会在查询等地方用它 在两者之间转换
接下来 我将在查询中为 CalendarManager添加@Dependency属性 这是CometCal的数据层 处理所有SwiftData操作 @Dependency属性包装器 是App Intents将共享资源 注入intents和queries的方式 因此无需创建新实例 它提供我注册一次的相同对象 就像我在右边所做的
CalendarManager是 @MainActor隔离的 所以我也将查询结构体 标注为@MainActor 我将实现EntityQuery协议 所需的方法 这通过CalendarManager 依赖项按ID获取日历 现在 EntityQuery涵盖了 系统已知实体ID的情况 但稍后 在创建活动时 系统需要知道哪些日历可用 让Siri可以将其作为选项提供 为此 我将遵循 EnumerableEntityQuery 并添加allEntities方法 返回所有日历
对于DisplayRepresentation 我将标题设置为日历标题 并将图像设置为 日历的系统图像 Siri和Spotlight 在显示实体时使用此项 这是一个准备就绪的实体…… 差不多了 还有一处容易遗漏的地方 IndexedEntity定义了 我的索引内容的结构 但实体仍需被捐献 为此 我将打开CalendarManager文件 每当日历或任何索引实体发生更改 索引就需要更新 为此 我有一个 CSSearchableIndex实例 在CalendarManager的 初始化器中初始化 为CometCal使用唯一名称 在createCalendar方法中 即将返回新日历之前 我将通过调用indexAppEntities 来捐献实体 使用之前的searchable index实例
同样 在updateCalendar方法中 我将索引已更新的日历实体
在deleteCalendar方法中 我将通过调用deleteAppEntities 从索引中移除实体 传入实体的id和类型
我将启动引擎 试一试…… 我将打开CometCal并创建 一个名为「Lunar Orbit Log」的新日历
然后 我向下滑动 搜索「Lunar Orbit Log」……
就出现了 带有日历图标和标题 CalendarEntity已就绪 后续两个实体遵循相同模式 但每个都引入了新内容 这是一个名为AttendeeEntity的 新文件 已导入AppIntents 与之前模式相同…… 输入calendar_attendee 并选择代码片段 熟悉的部分相同…… 这些都已在幕后连接好 这样我可以继续快速推进 欢迎在此暂停 查看已完成的源代码 与CalendarEntity不同 AttendeeEntity遵循 TransientAppEntity协议 而非IndexedEntity 这是有意为之 TransientAppEntity表示 一个临时实体 不需要唯一标识符 也不用于查询 这正好适用于此处 在CometCal中 与会者代表 一个人对特定活动的参与 而非该人本身 同一人可以参加多个活动 而单独索引每次参与 会在Spotlight中产生重复结果 由于与会者始终通过其所属活动访问 无需独立的查找路径 TransientAppEntity明确了这一点 无需编写查询 无需维护索引 与会者具有Schema要求的属性 如用于表示此参与是否为可选的 Boolean属性
一个新项目是IntentPerson类型…… 这是系统表示人员的标准方式 包含姓名和联系信息 在App之间共享此数据时非常有用 如将与会者的电子邮件地址发送 到邮件App以起草消息 Schema还包含两种@AppEnum类型 Schema定义了可能的情况集合 我的App采用其中适用的情况 这些也是Schema化的 我将用代码片段快速创建 我将使用calendar_attendeeStatus 代码片段来设置状态 该代码片段包含了Schema 支持的所有情况 CometCal的模型已直接映射 无需更改 但如果App使用不同的术语 只需将现有模型映射到 Schema的情况 这样Siri就能识别其结构 我将使用calendar_attendeeType 来设置与会者类型 Schema要求至少一种情况来 描述与会者的类型 由于CometCal的与会者都是人 我将添加person情况
我将分别填入状态和类型字段
这样就完成了AttendeeEntity
已有两个实体就位 还有一个待发射 日历和与会者实体 将随下一个找到各自的轨道…… 将它们全部聚合在一起的 引力中心…… EventEntity 系统搜索索引在活动实体上 表现尤为出色 当有人问 「我的团队午餐是几点?」时 Siri可以搜索标题 当他们问 「哪些活动提到了氧气?」时 Siri可以搜索备注内容 用户可以询问有关自己数据的问题 Siri会直接回答 这是已应用calendar_event 代码片段的EventEntity 就像CalendarEntity一样 EventEntity遵循IndexedEntity协议 并在CalendarManager中包含索引 以利用语义索引 这里内容很多 Schema涵盖了大量属性 但别担心 之前实体的 相同模式同样适用 主要区别在于参数的 数量和种类 为了让这个任务按时完成 熟悉的部分已在幕后连接好 欢迎暂停查看源代码 Schema定义了哪些属性是必需的 哪些是可选的 如title或startDate等基本属性 连接起来很简单 我的App不使用的可选属性 如travelTime或virtualLocation 可以保持未设置状态 不属于Schema但存在 于数据模型的属性 如isFavorite 也可以添加到实体中 这个实体有趣之处在于 它如何与之前构建的 其他实体组合 该活动所属的日历 是一个CalendarEntity……
与会者是一个AttendeeEntity数组
Siri通过App Schemas 理解这些关系
recurrence属性也值得快速提及 它可用于表示重复发生的活动 如每周锻炼或重要的 年度纪念日 它使用Foundation的 Calendar.RecurrenceRule类型 并与CometCal的 简单频率枚举相互转换 适用于每日 每周 每月或每年等情况
作为Schema的一部分 还有活动位置和提醒的联合值 联合值是一个可以持有 多种不同类型之一的属性 例如 位置可以是PlaceDescriptor 来自GeoToolbox框架或String 同样 我们可以通过代码片段简单实现
就像这样 对于提醒 可以是Duration或Date
相应地将属性赋值为这些类型
与与会者类似 这里也有Schema化的枚举 如EventEntityStatus 与活动相关的两个枚举 都从代码片段中完整生成 所以我将添加它们并连接好
最后 将状态属性赋值为 新的状态类型 这样 内容层已完全就绪 准备发射
是时候看看它是否能够升空了! 左边是流星雨观赏派对的详细视图 时间 地点和备注都显示在屏幕上…… 但想象我正在与Siri对话 谈论流星 然后突然想起了这个派对 无需离开对话打开CometCal 查找详情 我只需询问…… 「流星雨派对快要到了吗?」
「那里的天气怎么样?」
我也可以切换到打字 「最佳观看时间是什么时候?」
我还可以轻点结果跳转到CometCal
Siri使用App内容回答每个问题 无需自定义自然语言…… 只需实体和Schema 你可能已经注意到了 从我与Siri的对话中轻点活动 会打开CometCal 但它只停留在主屏幕 并未如预期那样导航到该活动 Siri还不知道如何在App中 打开特定活动 我将做一个小迂回 以进一步改善体验 为此 这里有一个OpenEventIntent 这是遵循system.open Schema 的小型intent 它以EventEntity作为目标 并告知NavigationManager 导航到该活动 每当有人在Spotlight或Siri中 轻点活动结果时系统都会调用此操作 或请求Siri打开一个活动
就这样! 现在如果我轻点一个活动…… 这次 CometCal直接打开到详细视图 显示我的流星雨观赏派对 这就是OpenIntent弥合差距的方式 Siri现在可以比以往 更好地理解App内容 人们可以向Siri询问任何内容 只用三个结构体和 几个代码片段 不错吧? 还有一件事可以让我与Siri的对话 感觉更加自然 当某人在屏幕上显示特定活动时 他们可能想说 「给这个活动的人发邮件」 而无需说出活动标题 这就是屏幕感知能力…… 只需两个视图修饰符 在CometCal的CalendarListView中 列出了我所有的活动 我将向列表添加 .appEntityIdentifier修饰符 为每个活动实体传入 EntityIdentifier 这将列表与其实体连接起来 当有人浏览列表时 系统知道屏幕上显示了哪些活动 在活动详细视图中 当单个活动的详情显示在屏幕上时 我将附加一个带有EntityIdentifier 的.userActivity修饰符 这告知系统某个特定活动处于前台 这样Siri可以将此活动精确解析为 正在查看的活动
就这样!下面是它能实现的功能 现在Siri可以了解屏幕上的内容 并打开活动实体 我将以自然方式请求Siri打开活动 「Hey Siri 打开第三个活动」
现在我在流星雨观赏派对的 详细视图上 我将尝试引用此活动 而不说出完整标题…… 「Hey Siri 给这个活动的人发邮件 让他们带巧克力和棉花糖来」
Siri可以利用对屏幕上活动的理解 找到与会者并传递给邮件App
真的……只需两个修饰符…… 就可以将屏幕上的内容 与App内容连接起来 Siri现在可以谈论这些内容了 但要真正起飞 我将赋予Siri 执行操作的能力 再次 App Schemas引领方向 这正是事情变得真正有趣的地方 我将从创建活动开始 就像实体一样 intents也使用代码片段 我将找到calendar_createEvent 代码片段并选择它 它使用@AppIntent宏 搭建intent的框架 Schema 所有Schema所需的参数 以及perform存根 从title到note的参数 来自Schema 我可以在intent的 perform逻辑中使用它们 我将首先填入类型 然后我将添加@Dependency 为CalendarManager使用 在perform方法中 在perform方法中 我将标注@MainActor 并将EventEntity设为返回值类型
通用模式很简单 将intent的参数解析为 数据层能理解的内容 执行操作 并以实体形式返回结果 对于创建日历活动 这意味着解析参数 如从联合值中提取位置
以及在提供时转换recurrence 我将把所有内容传递给 calendarManager的createEvent方法 并以EventEntity形式返回结果
这是令人惊叹的地方 由于这遵循了App Schema Siri可以处理所有繁重工作 解读语言 要求澄清 并确认详情…… 这样人们就可以与Siri 自然对话了 是时候发射了…… 想象一下我想创建一个新活动 但我正在进行太空行走 iPhone漂在够不着的地方 我不去拿它 而是直接问Siri…… 「Hey Siri 在Lunar Orbit Log中 创建一个新活动」
「叫做Zero Gravity Yoga 定在6月15日上午8点」
Siri可以解析新活动的标题 日期和时间 完成后 活动就会添加到日历中
就这样 几行代码 让App与Siri协同工作 这就是App Schemas的力量 现在活动可以通过Siri创建了 我将继续保持势头 处理更新活动 这是UpdateEventIntent 已使用calendar_updateEvent 代码片段填写完毕 结构与create intent类似 但关键区别在于 大多数参数都是可选的 因为用户可能只更改一两项内容 event参数是Siri解析的内容 其他都是可选的 perform逻辑遵循相同模式 如果提供了参数就解析它 然后将所有内容传递给 CalendarManager的updateEvent方法 并返回已更新的活动 代码可能比create intent多 但这里没有任何全新的东西
但有一个重要的细节 与更新intents中可选参数 相关值得关注 例如 当recurrence为nil时 这意味着「不更改」还是「删除」? 简单的nil检查 无法告诉我是哪种情况 仔细看perform方法中的 recurrence逻辑 @AppIntent宏将每个属性 包装在IntentParameter中 它公开了一个valueState 这就是我区分的方式 带有实际值的.set表示提供了新值 带有nil值的.set表示已明确清除 .unset表示该参数不在请求中 此模式适用于任何可选参数 其中清除值是有意义的操作 现在更新intent已连接好 我将发送几个命令进入轨道 「Hey Siri 把这个移到晚上10点」
「好的 没问题」
「另外 将这个改为每周重复 并移到我的Deep Space日历」
「好的」
「其实 不要重复这个活动」
详细视图显示了每项更改 都源自几行代码和一个App Schema
更新有效 但Siri显示了默认结果卡 这是CometCal 它理应 有更多……氛围 默认情况下 Siri从显示表示 构建结果卡 Snippet视图可让我 用自定义SwiftUI视图替换它 我已准备了一个SwiftUI视图 它接受EventEntity 并以出色的方式呈现详情 你在这里可以非常有创意…… 但也要保持简洁轻量 为了连接好这一切 我将打开UpdateEventIntent
我将添加ShowsSnippetView 到perform方法的返回类型 然后在return语句中 我将传入EventSnippetView 相同方法适用于 任何返回结果的intent 如create intent
「Hey Siri 将团队午餐延后一小时」
我现在得到了新的Snippet! 宇宙渐变色调 深蓝色背景 更新后的活动详情和星形图标…… 这就是App个性在Siri中的展示 这就涵盖了更新intent 最后要连接的操作是删除 它是三个中最简单的 这是DeleteEventIntent 这非常简单…… 只需活动和用于重复活动的可选span perform逻辑找到活动并将其删除 Siri在删除任何内容前 自动处理确认对话框
是时候测试弹射序列了 「Siri 删除那个派对」
「是的 删除它」
「另外 删除6月9日的活动」
「哦……实际上 算了吧」
Siri在删除任何活动前请求确认 并在多个活动匹配时进行消歧 三个intent 通过Siri进行完整的活动管理 任务完成! CometCal已从屏幕限制 变为完全语音控制 这个视频中构建了很多内容 以下是让你自己的App 与Siri协作的后续步骤 下载CometCal示例项目 探索完整实现 浏览App Intents文档 探索所有可用的App Schemas和域 对于自动化测试 查看这个视频 了解如何为CometCal编写测试 使用新的AppIntentsTesting框架 观看「Explore advanced App Intents features for Siri and Apple Intelligence」 深入了解改进App与Siri 协作方式的方法 这些内容在本视频中未曾涉及 你现在已准备好 将App带到最前沿 非常感谢你的跟随学习!
-
-
- 0:00 - Introduction
Justin Kang previews the goal: take an existing app and make it available to Siri. A picnic scenario shows Siri searching events, updating times with confirmation, texting attendees, and answering questions, all through conversation.
- 1:43 - App Schemas and the plan
Apps integrate with Apple Intelligence through App Intents, and App Schemas describe content and actions in terms Siri already understands, no training phrases or NLP. Schemas are organized into domains (here, the calendar domain). Introduces the CometCal sample app and the two goals: understand content, and perform actions.
- 3:44 - Build the CalendarEntity
Create a schematized @AppEntity from the calendar_calendar snippet, set the id to UUID, conform to IndexedEntity, wire a @Dependency and @MainActor query (EnumerableEntityQuery with allEntities()), set a display representation, and donate to Spotlight via indexAppEntities and deleteAppEntities.
- 8:00 - Build the AttendeeEntity
Built from calendar_attendee, but conforming to TransientAppEntity instead of IndexedEntity, attendees are accessed only through their event, so they need no identifier, query, or index. Introduces the IntentPerson type and two schematized @AppEnums (calendar_attendeeStatus, calendar_attendeeType).
- 10:30 - Build the EventEntity
The central IndexedEntity (from calendar_event), where the semantic index shines for title and note-content questions. Composes the CalendarEntity and [AttendeeEntity], handles recurrence (Calendar.RecurrenceRule), union values for location and alarms, and event status/span enums.
- 14:34 - Open events with OpenIntent
A small OpenEventIntent conforming to the system.open schema takes an EventEntity and tells the NavigationManager to navigate to it, so tapping an event in Spotlight or Siri opens straight to its detail view.
- 15:30 - Onscreen awareness
Two view modifiers connect the screen to entities: .appEntityIdentifier on the event list and .userActivity (with an EntityIdentifier) on the detail view, letting Siri resolve "this event" or "that third event" without naming the title.
- 17:18 - Create events with Siri
Build CreateEventIntent from calendar_createEvent: fill in parameter types, add a @MainActor @Dependency, and in perform() resolve the schema parameters (location union value, recurrence) into the data layer and return an EventEntity. Siri handles language, clarification, and confirmation.
- 19:24 - Update events
UpdateEventIntent (calendar_updateEvent) mostly mirrors create, but parameters are optional. The key subtlety: an IntentParameter's valueState distinguishes .set with a value (change it), .set with nil (explicitly clear it), and .unset (not part of the request).
- 21:30 - Custom snippet views
Replace Siri's default result card by adding ShowsSnippetView to the intent's return type and returning a custom SwiftUI EventSnippetView, bringing the app's visual personality (cosmic gradient, star icon) into Siri.
- 22:30 - Delete events
DeleteEventIntent is the simplest, just the event plus an optional span for recurring events, and Siri automatically handles confirmation and disambiguation before deleting.
- 23:35 - Next steps
Download the CometCal sample, browse the App Intents documentation for all schemas and domains, write tests with AppIntentsTesting, and watch "Explore advanced App Intents features for Siri and Apple Intelligence."