大多数浏览器和
Developer App 均支持流媒体播放。
-
深入了解 App Intents
了解如何在使用 App Intents 框架时提高您的 App 的曝光度和吸引力。我们将介绍此 Swift 框架的强大功能,探索 App Intents 和 SiriKit Intents 之间的差异,并说明如何向系统公开您 App 的功能。我们还将分享如何构建实体和查询,来打造丰富的 App 快捷指令体验。要进一步了解 App Intents,请观看 WWDC22 的“利用 App Intents 实施 App 快捷指令”和“设计 App 快捷指令”。
资源
相关视频
WWDC23
Tech Talks
WWDC22
WWDC21
-
下载
♪ 柔和乐器演奏的嘻哈音乐 ♪ ♪ 嗨 朋友们 我是 Michael Gorbach 来自快捷指令工程团队 感谢您收看本次对 App Intents 的深度解析 App Intents 是 我们新推出的框架 用于向系统注册 App 的功能 以下是今天深度解析讲座的安排 简单介绍后 我将讨论意图及其参数 以及应该如何定义实体 我将介绍几种您可以创建的 强大的搜索和过滤功能 以及您的意图如何与用户进行交互 最后 我会谈一谈 App Intents 架构和生命周期 让我们从头开始 在 iOS 10 中 我们 引入了 SiriKit Intents 框架 它可以让您将 App 的功能 连接到 Siri 域 例如消息传递 锻炼和支付 现在我们将引入一个新框架 它就是 App Intents 它有三个关键组成部分 意图是内置于 App 中的操作 它们可以在整个系统中使用 意图使用实体 来表述 App 的概念 App 快捷指令包装意图 使它们自动化并可被发现 让我们来谈谈 利用 App Intents 使您的 App 功能 在更多地方可用 并让您的用户受益的几种方法 借助 App 快捷指令 每个人都可以 通过 Siri 以声音使用 App 的功能 无需提前设置任何东西 相同的采用方式 也在用户搜索您的 App 以及您的 App 得到推荐时 使意图出现在 Spotlight 中 这将使您的工作处于核心位置 使用 App Intents 您还可以创建“焦点筛选” 让客户能够针对特定的“焦点” 对您的 App 进行自定义 例如 他们可能会将 自己的“日历”App 设置为 仅在实际工作时显示工作日历 您可以查看这个讲座来了解更多关于 如何运用“焦点筛选”的信息 使用 App 快捷指令 您的意图会自动 显示在“快捷指令”App 中 无需手动添加 将操作集成到“快捷指令”中 对客户来说非常有价值 因为他们可以通过运行快捷指令 使用您的 App 来自 整个系统各个位置的功能 在主屏幕上轻点一下 即可运行快捷指令 也可通过 macOS 的菜单栏 以及其他众多方式运行 甚至可以通过自动化设置 使快捷指令自动运行 将您的 App 接入 整个“快捷指令”生态系统 借助 Apple 的 一系列 App 和其他开发者的强大力量 对快捷指令进行支持 将使您的 App 威力倍增 这是因为快捷指令可以组合 来自多个 App 的操作 让用户创造出全新的特性和功能 而无需您进行任何开发 如果想学习如何使操作 与他人配合默契并无缝融入生态系统 请查看我们的设计演讲 我们创建 App Intents 的目标 是为了让它的开发成为一种快乐 App Intents 十分简洁 编写一个简单的意图 只需要几行代码 但 API 也可扩展出 更深入和更定制化的操作 App Intents 非常现代化 我们对 Swift 全力投入 充分利用结果构建器 属性包装器 面向协议的编程和通用算法 没有尖端的语言特性 这些 API 根本不可能存在 使用 App Intents 也很容易 因为它既不要求重新架构 您的产品和目标 也不需要创建框架 它不需要扩展 并且可以直接应用于您的 App 同时 App Intents 代码是可维护的 与 SwiftUI 一样 App Intents 将您的代码 视作事实的根本来源 避免了对独立编辑器 或定义文件的需求 让您可以快速创建和迭代采用 并简化维护 因为它们都在同一个位置 话说回来 让我们来 探讨一下这些新 API 就从意图开始 它我们新框架的核心组成部分 App 意图 或简称“意图” 是一个单体的 独立的功能单元 由您的 App 向系统公开 比如说 一个意图可以 创建一个新的日历事件 打开一个特定的屏幕 或者下一份订单 意图可以根据用户请求运行 例如通过快捷指令或询问 Siri 也可以是自动的 例如使用“焦点筛选” 或自动化“快捷指令” 当意图运行之后 要么返回结果 要么出现错误 意图包含三个关键部分 元数据 也就是有关意图的信息 也包括本地化的标题 参数 这是意图运行时可以使用的 输入值 还有 perform 方法 它负责 意图被执行时的实际工作 今天我们将从这个“图书馆” App 出发进行讨论 因为我很痴迷读书 这个 App 完全是 用于追踪我读过的书 以及想读或正在读的书 每个类别都作为 单独选项卡显示在 App 中 我称这些选项卡为“书架” 我的用户随时都在访问 “正在阅读”书架 所以我要公开一个 App 意图 让打开书架变得更快 更方便 我准备在这里创建一个 OpenCurrentlyReading 意图 通过定义一个 符合 AppIntent 协议的 Swift 结构来实现 我只需要实施一种方法 即 perform 我的 App 中已经有了 可以打开标签的导航器 所以对我来说 实现意图 只是几行代码的事 我将使用 @MainActor 来注释 perform 方法 因为我的导航器需要主线程 我的意图也需要一个标题 和我今天要展示的其他字符串一样 如果将键添加到字符串文件中 它将自动本地化 要让一个基本的 App 意图生效 我只需要做这些就够了 由于我已经在代码中定义了它 它将自动出现在快捷指令编辑器中 在这里 用户可以将它 添加到快捷指令 仅仅公开意图 就可以带来巨大的影响力 因为一旦客户把意图变成快捷指令 它就可以在系统中的多个位置使用 包括屏幕上所有这些 为了让我的新意图更容易 得到使用和发现 我还将添加 对 App 快捷指令的支持 只需要一点代码 我就可以让意图在 Spotlight 和“快捷指令”App 中 中自动显示 我还可以定义用户在借助声音 使用这个意图时 对 Siri 说的短语 请查看“使用 App Intents 实现 App 快捷指令”讲座 获取全部详细信息 现在 我已经公开了一个 可以打开“正在阅读”书架的意图 接下来 让我们对它进行泛化 添加一个参数 使它可以打开任意书架 我有一个代表货架的枚举 为了将其用作意图参数 我需要使它符合 AppEnum 协议 AppEnum 需要 一个字符串原始值 所以我先把它加上 它还要求我为每个枚举案例 提供可本地化的 人可读的标题 这些必须作为字典文字提供 因为编译器将在构建时读取此代码 最后 我将添加 typeDisplayName 将枚举类型视作一个整体的 一种用户可见的 可本地化的名称 我将使用“书架” 在一个意图中 每个参数都借助 @Parameter 属性包装器的 使用进行声明 依靠有关参数的信息进行初始化 就像标题一样 在这里 我定义了一个新的书架参数 由我的 perform 方法 中读取 参数支持所有这些类型 包括数字 字符串 文件等等 以及您 App 中的实体和枚举 这是此意图在 “快捷指令”编辑器中的外观 请注意 书架参数 出现在了一个表格行中 我可以通过使用 ParameterSummary API 让 UI 更精简 并使其更适合“快捷指令” “参数摘要”是在编辑器中 表述您的意图及其参数的 一个语句 例如“打开 ” 为了在“快捷指令”中获得最佳效果 您应当为创建的每一个意图 提供“参数摘要” 您也可以定义哪些参数 将显示在折叠区域下 哪些是隐藏的 这些 API 可以做很酷的事情 比如依据您意图任何参数的 实际值来改变摘要 只需使用 When 和 Otherwise API 或者 Switch Case 和 Default API 为了添加参数摘要 我实施了这项静态属性 在这里 我将返回字符串“打开” 并插入书架参数 要让“打开书架”生效 我需要做的最后一件事 就是确保意图在运行时 打开“图书馆”App 就像这样 打开 App 的操作由静态属性 openAppWhenRun 控制 它默认设置为假 这对大多数意图而言都有效 但对于那些需要在 UI 中 打开某些内容的意图 比如眼下这一个 就需要将其设置为真 我刚才创建了一个打开书架的意图 它非常简单 因为书架的集合是固定的 那么如果我想创建一个意图来打开 集合为动态而非固定的“书本” 该怎么做呢? 为此 我需要实体 实体是您的 App 向 App Intents 公开的概念 当值为动态或由用户定义时 您应该使用实体而非枚举 例如“备忘录”中的笔记 或“照片”中的图片或相册 要提供实体的实例 您的 App 可以实施查询 并返回实体作为意图的结果 首先 我要在 App 中 创建打开一本书的意图 在“快捷指令”编辑器中 它看起来应该是这样 当用户轻点“书本”参数时 就会获得一个选取书目的选择器 也包括由我的 App 提供的 一组推荐实体 用户还可以使用选择器顶部的搜索栏 在自己的图书馆中找到任何书籍 在我建立意图本身之前 我需要先创建一个书本实体 及相应的查询 一个实体至少包含三个部分 标识符 显示表述 和实体类型名称 要添加实体 首先要使结构 符合 AppEntity 协议 在这里 我将为 BookEntity 定义一个新结构 但也可以用我模型中 现有的类型执行符合 通过符合实体来向可识别协议 提供标识符 App Intents 使用此标识符 来引用您的实体 因为它在您的 App 和系统其他部分之间传递 标识符应当是稳定而持久的 因为它可能被保存在由您的 客户创建的快捷指令中 显示表述的作用是 向用户展示此实体 它可以像一串文本一样简单 比如书名 您也可以为它提供副标题和图像 typeDisplayName 是一种 人可读的字符串 用来表述实体的类型 在这个案例中 它就是“书本” 现在 为了完善书本实体 我需要添加查询 查询为系统提供了一个接口 用于从您的 App 中检索实体 查询可以通过几种方式寻找实体 所有查询都需要能够基于标识符 对实体进行查找 字符串查询支持搜索 稍后您还会看到属性查询 它的灵活度更大 所有查询都可以提供建议实体 允许用户从列表中选择 每个实体都应当与一个查询相关联 使系统可以查找该实体的实例 您可以通过 创建 Swift 结构来提供 符合 EntityQuery 协议的查询 基础查询只需要实施一种方法 在给定标识符数组时 用于解析实体 我实现的方式是访问模型数据库 并找到与标识符匹配的任何书目 现在 我需要将查询连接到实体 可以通过在 BookEntity 类型上实施 defaultQuery 静态属性 并返回 BookQuery 的 一个实例来完成 当用户选择一本书时 它的标识符将被保存到快捷指令中 快捷指令运行时 App Intents 会将标识符 传递给我的查询 用于检索 BookEntity 实例 现在 BookEntity 类型 已经符合 AppEntity 协议 我可以将它用作 OpenBook 意图中的参数 perform 方法 使用我的导航器 找到这本书
为了支持书本选择器 我的查询还需要提供建议结果 为此 我需要在 查询中实施另一种方法 返回我的“图书馆” App 里添加的所有书 快捷指令将用这些结果填充选择器 请注意 “快捷指令” UI 顶部有一个搜索框 我的 App 中 可能有大量书本实体 所以我非常需要 在 App 进程中运行 直接针对数据库的搜索 StringQuery API 可以帮我做到 采用 StringQuery 子协议需要我 实施另一种方法 即 entities (matching string:) 来返回给定字符串的结果 在这里 我实现了简单的 不区分大小写的 书名匹配 但我也可以做实现更复杂的功能 比如说 使用作者或系列名称 来进行搜索 如果我的书总量很大 但收藏夹里的数量少一些 在 suggestedEntities 中可以 只返回收藏夹内容 并借助 entities (matching string:) 允许用户在更长的列表中进行搜索 我已经公开了一种 在 App 中打开书本的方法 并在此过程中创建了 书本实体和书本查询 现在就可以使用同样的 实体和查询来创建更多意图 我的下一个任务是创建 将书本添加到图书馆的意图 客户在线浏览的同时 可以使用共享表单快捷指令 快速添加书本 也可以呼唤 HomePod 上的 Siri 来添加书本 甚至不需要看屏幕 像这样创建直接操作模型 而不显示 UI 的意图 可以真正为您的用户赋权 这是我对 AddBook 意图的实现 参数是书名和 可选的作者姓名 它还包含了可选的备注 用于记录是哪个朋友推荐了这本书 perform 方法 通过使用 async/await 的 API 调用来查找 并将书本添加到图书馆 如果找不到匹配项 将引发错误 为了将这个错误本地化 我使错误类型符合 CustomLocalizedStringResourceConvertible 协议 我将从属性返回本地化的字符串键 并将键添加到我的字符串文件中 配合 Siri 小组件等等工具 “添加书本”意图 已经非常有用了 但如果将它与其他意图相结合 它的灵活度将进一步提高 只需稍加改动 我就可以使“添加书本”意图与 我之前创建的“打开书本”组合 将结果从一个传递到另一个 为此 我将让“添加书本”意图 返回一个值作为其结果的一部分 请注意我的 perform 方法的返回类型 已经选取了一个新协议 来表述我返回的值 现在 用户可以将这个意图的结果值 与以书本实体为参数的 其他意图连接起来 “添加书本”意图 和“打开书本”意图 就很自然地配合起来 让您可以创建快捷指令来添加一本书 随后立即在库中打开它 一种常见模式是从意图返回结果 并在 App 中打开它 App Intents 中有一种 名为 openIntent 的内置方式 来表述这种模式 如果我添加了 openIntent 则客户会在 “快捷指令”中获得一个 名为“运行时打开”的新开关 如果他们关掉开关 他们将能够在不被打断的情况下 在后台将这一意图 作为快捷指令的一部分使用 如果他们打开开关 新添加的书 将立即在“图书馆”App 中打开 采用 openIntent 就像 为“打开书本”意图创建实例 并将其作为部分结果返回一样简单 当意图运行时 如果“运行时打开”状态为开 “打开书本”意图将在 “添加书本”意图完成后自动执行 借助实体和查询 还可以实现更多功能 使用以下这组 API AppIntents 开启了一些在 SiriKit Intents 框架下 从未有过的强大性能 让我们来看看如何公开 更多实体信息 并允许客户据此进行查找和筛选 到目前为止 我已经将所有基础请求 添加到了我的书本实体中 但要想让用户更深入地将书本 整合到快捷指令中 我将需要公开更多书的信息 实体支持属性 属性则持有您想向用户公开的 关于该实体的额外信息 在本例中 我将添加书本的作者 出版日期 阅读日期和推荐人 这样用户就可以 在快捷指令中使用这些属性 我将使用名为 @Property 的属性包装器 来将属性 添加到 BookEntity 属性支持与参数相同的所有类型 且每个种类都有本地化的标题 有了这些新属性 我的客户现在可以在 “快捷指令”中使用魔法变量 提取与书本实体配合时的 每一条新信息 当使用此前创建的 “添加书本”意图时 用户可以在快捷指令中用到 新增书籍的作者或出版日期
当您将属性与查询结合使用时 借助这个灵活的谓词编辑器 UI 您的 App 将在 “快捷指令”中自动获得 难以置信的强大“查找与筛选”操作 现在 我的客户将能够基于 阅读日期 标题 作者等条件 查找和筛选书本 比如说 要找到 Delia Owens 的所有书 可以说是小菜一碟 使用排序依据和限制选项 您可以支持更高阶的查询 比如找到 Delia Owens 最近出版的三本书 客户可以使用这些组成部件 做一些很酷的事 比如查找 自己的收藏中最常见的三位作者 为了实现这些 我需要采用另一种查询 即属性查询 属性查询并非基于字符串 或标识符对实体进行查找 而是基于实体内的属性 实施属性查询分为三个步骤 首先 声明查询属性 指定如何通过属性对实体 进行搜索 接下来 填加排序选项 定义如何对查询结果进行排序 最后 实施 entities(matching:) 并运行搜索 查询属性 声明了 AppIntents 可以在与此查询关联的 实体上进行搜索的所有方式 每种都列出了实体的一个属性 和可用于它的比较运算符 例如包含 等于 或小于 在这里 我为日期属性列出了 “小于”和“大于”比较器 为书名属性列出了 “包含”和“等于” 查询属性将每个属性组合 和比较器映射为您选择的类型 这被称为比较器映射类型 在这里 因为使用了 CoreData 所以我选 NSPredicate 来用 如果使用的是 自定义数据库或 REST API 可以设计自己的比较器类型 并且选择使用它 这是用于设置书本查询属性的代码 我使 BooksQuery 符合 EntityPropertyQuery 协议 然后使用 QueryProperties 结果构建器 实施静态变量属性 每个条目都指定一个可查询的 属性 keyPath 在它内部 是适用于该属性的所有比较器 对于每个比较器 我都提供一个 NSPredicate 因为我选择了 NSPredicate 作为比较器映射类型 当系统要求我的 App 返回查询结果时 它将再次提供我在这里 创建的 NSPredicates 排序的定义也与之类似 这是一张所有属性的列表 我的模型可以用这些属性 对书本进行排序 在这个案例中 我允许按书名 阅读日期和出版日期排序 最后 我实施 entities(matching:) 它会查询我的数据库 并返回匹配的实体 此方法采用了比较器映射类型的 一个我在之前定义 查询参数时使用的数组 在这个案例里 就是 NSPredicate 这些谓词描述了我想通过 实体属性的哪些标准来查询 它也采用了一种模式 来指示是否要使用 “和”及“或”来合并谓词 进行排序的关键路径 以及结果数量的可选限制 我在实施时使用这些参数 对 CoreData 数据库 执行查询 那么客户可以使用 此属性查询做什么呢? 可以从库中随机挑选一本书来读 也可以找到所有出版于 20 世纪初的书 可以借助“快捷指令”生态系统 通过将自己的 App 与其他人的连接 使自己的 App 变得更有用 例如 可以使用电子表格 App 将今年阅读的所有书 导出为 CSV 文件 也可以使用绘图 App 制作图表 呈现过去的 10 年里 每年读书的数量 而这仅仅是个开始 这种深入的 App Intents 运用 真正让客户使用您的 App 去完成他们期待它实现的功能 让您的 App 成为他们 工作流程的关键部分 所有这些集成 比如制作图表 都是您不必费心去构建的功能 当您的意图被执行时 您的 App 可能需要 与用户进行交互 去展示或说出结果 或解决歧义 无论是 Siri 请求 还是快捷指令 App Intents 支持几种此类交互 “对话”用于意图完成时向用户 提供文本和语音反馈 “片段”则用于提供视觉反馈 “请求值”和“消歧义” 用于要求用户澄清意图参数的值 “确认”用于对参数值进行验证 或与用户核实 意图是否具有交易性或破坏性 “对话”向运行意图者 提供口头或文字回应 提供对话对于使意图在 语音体验中发挥作用真的非常重要 在我之前创建的“添加书本”意图中 我将添加 needsValueDialog 在询问书名时说出 还要添加针对 perform 方法返回结果的对话 对话将在我们的不同平台上 由“快捷指令”或 Siri 来朗读或显示 您可以将“片段” 视为“对话”的视觉等价物 它让您能够为意图的结果 添加视觉表述 要使用片段 只需添加您选择的 SwiftUI 视图 作为意图结果的尾随闭包 与小部件一样 SwiftUI 视图将被归档 并发送给“快捷指令”或 Siri App Intents 还支持 通过抛出 requestValue 向用户询问值 比如说 当您需要 一个有时是可选的参数值时 这就能派上用场 在这里 当字符串搜索 返回不止一本书时 requestValue 就可以帮上忙 在这种情况下 我提示并询问作者姓名 来缩小书本的搜索范围 requestValue 给出了 一个可以引发的错误 它将提示用户 并使用更新的作者姓名 重新运行该操作 同时 “消歧义” 在您需要用户在参数的一组值之间 进行选择时非常有用 它为我提供了一种更好的方式 来处理“添加书本” 操作中的多个可能结果 在这里 我从生成的书本中 获取了作者姓名列表 并要求对这些可能的值进行消歧 用户将被要求在它们之中进行选择 而我会得到结果 最后 App Intents 支持 两种不同的确认方式 第一种是参数值的确认 您可能会在心里对值已有大概猜想 但仍想证实的情况下 使用这种确认方式 在添加书本时 有时我调用的按书名查找网络服务 会返回几个匹配项 但其中有一项在目前看来更受欢迎 在这样的情况下 我会假设 用户打算添加那本受欢迎的书 但我会加上“确认”来保证我猜对了 为此 我将在书名参数上 调用 requestConfirmation 第二种方式是确认 意图的结果 举例来说 它非常适合用于下订单 如果我想通过 “图书馆”App 获利 并通过书店添加订购 我需要确保订单信息正确 为此 我可以在意图上 调用 requestConfirmation 来传递要下的订单 我也会在这里指定片段 来显示订单的预览 我在调用前加上了 try 因为 requestConfirmation 在用户取消而不是确认的情况下 将引发错误 在结束讲座前 我还想向您介绍 App Intents 架构的 几个方面 这些是您在采用此框架时 就应该了解的 实际上 有两种构建 App Intents 的途径 在您的 App 中 或者在单独的扩展程序中 直接在您的 App 中实施意图 是最简单的 这种方法很好用 因为您不需要框架 不用复制代码 也不需要跨流程进行协调 使用您的 App 还能够 提供更高的内存限制 让您有能力完成一些 在扩展程序中更难实现的功能 比如播放音频 如果在意图返回“真”的情况下 实施 openAppWhenRun 您的 App 就可以在前台运行 否则 它将在后台运行 在后台运行时 您的 App 将以特殊模式启动 无需调出场景来将性能最大化 事实上 如果您在 App 中 实施后台 App 意图 我们强烈建议您也实施场景支持 或者 您也可以在扩展程序中 构建您的 App 意图 这种方式有几个优点 它更轻量 因为扩展程序进程 只处理 App 意图 也不需要启动您的 App 如果您正在处理“焦点”意图 使用扩展程序也意味着您可以 在“焦点”改变时 立即使意图 在扩展程序上执行 而无需要求 App 首先在前台运行 扩展程序的工作量更大 因为您需要添加一个新目标 将部分代码转移到框架中 并处理您的 App 和扩展程序之间的 协调配合 要创建 App Intents 扩展程序 在 Xcode 中转到 “文件”> “新目标” 并选择“App Intents 扩展程序” 使用 App Intents 时 您的代码是唯一的事实来源 App Intents 通过静态提取构建时 您的意图 实体 查询和参数信息 实现了这种优雅的开发者体验 Xcode 将在您的 App 中 生成元数据文件 或构建过程中的扩展包 包含了从 Swift 编译器接收到的信息 因为它基于您的代码运行 为确保这些都能起作用 请将您的 App Intents 类型 直接保留在目标或扩展程序里 而非框架中 同样 您的本地化字符串 应该可以在与您的 App Intents 类型 同处一个包的字符串文件中找到 对于已有使用 SiriKit Intents 框架的 App 并考虑升级的人来说 如果您采用意图与小部件 或消息和媒体等域进行集成 请继续使用 SiriKit Intents 框架 但如果您要为 Siri 和 “快捷指令”添加自定义意图 您应当更进一步 升级到 App Intents 您可以通过在 SiriKit Intents 定义文件中 点按“转换为 App Intents”按钮 开始升级过程 使用 App Intents 将 您的 App 集成到“快捷指令”中 是将您作为开发人员的 影响力最大化的绝佳方法 只需通过少量工作 来运用 App Intents 您就将为客户创造大量的价值 感谢您参与今天的讲座 我非常希望您能 立刻试用 App Intents 并为我们提供反馈 我对这个新框架可以如何帮助您 通过 App 为人们带来 惊喜和快乐 并为其赋权 充满了期待 阅读愉快 希望您能 在 WWDC 有绝佳的体验 ♪
-
-
5:33 - Open Currently Reading
struct OpenCurrentlyReading: AppIntent { static var title: LocalizedStringResource = "Open Currently Reading" @MainActor func perform() async throws -> some IntentResult { Navigator.shared.openShelf(.currentlyReading) return .result() } static var openAppWhenRun: Bool = true }
-
6:42 - App Shortcuts
struct LibraryAppShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut( intent: OpenCurrentlyReading(), phrases: ["Open Currently Reading in \(.applicationName)"], systemImageName: "books.vertical.fill" ) } }
-
7:11 - Shelf Enum
enum Shelf: String { case currentlyReading case wantToRead case read } extension Shelf: AppEnum { static var typeDisplayRepresentation: TypeDisplayRepresentation = "Shelf" static var caseDisplayRepresentations: [Shelf: DisplayRepresentation] = [ .currentlyReading: "Currently Reading", .wantToRead: "Want to Read", .read: "Read", ] }
-
7:49 - Open Shelf
struct OpenShelf: AppIntent { static var title: LocalizedStringResource = "Open Shelf" @Parameter(title: "Shelf") var shelf: Shelf @MainActor func perform() async throws -> some IntentResult { Navigator.shared.openShelf(shelf) return .result() } static var parameterSummary: some ParameterSummary { Summary("Open \(\.$shelf)") } static var openAppWhenRun: Bool = true }
-
10:56 - Book Entity
struct BookEntity: AppEntity, Identifiable { var id: UUID var displayRepresentation: DisplayRepresentation { "\(title)" } static var typeDisplayRepresentation: TypeDisplayRepresentation = "Book" static var defaultQuery = BookQuery() }
-
12:29 - Book Query
struct BookQuery: EntityQuery { func entities(for identifiers: [UUID]) async throws -> [BookEntity] { identifiers.compactMap { identifier in Database.shared.book(for: identifier) } } }
-
13:16 - Open Book
struct OpenBook: AppIntent { @Parameter(title: "Book") var book: BookEntity static var title: LocalizedStringResource = "Open Book" static var openAppWhenRun = true @MainActor func perform() async throws -> some IntentResult { guard try await $book.requestConfirmation(for: book, dialog: "Are you sure you want to clear read state for \(book)?") else { return .result() } Navigator.shared.openBook(book) return .result() } static var parameterSummary: some ParameterSummary { Summary("Open \(\.$book)") } init() {} init(book: BookEntity) { self.book = book } }
-
13:40 - Suggested Entities and String Book Query
struct BookQuery: EntityStringQuery { func entities(for identifiers: [UUID]) async throws -> [BookEntity] { identifiers.compactMap { identifier in Database.shared.book(for: identifier) } } func suggestedEntities() async throws -> [BookEntity] { Database.shared.books } func entities(matching string: String) async throws -> [BookEntity] { Database.shared.books.filter { book in book.title.lowercased().contains(string.lowercased()) } } }
-
15:11 - Add Book Intent
struct AddBook: AppIntent { static var title: LocalizedStringResource = "Add Book" @Parameter(title: "Title") var title: String @Parameter(title: "Author Name") var authorName: String? @Parameter(title: "Recommended By") var recommendedBy: String? func perform() async throws -> some IntentResult & ReturnsValue<BookEntity> & OpensIntent { guard var book = await BooksAPI.shared.findBooks(named: title, author: authorName).first else { throw Error.notFound } book.recommendedBy = recommendedBy Database.shared.add(book: book) return .result( value: book, openIntent: OpenBook(book: book) ) } enum Error: Swift.Error, CustomLocalizedStringResourceConvertible { case notFound var localizedStringResource: LocalizedStringResource { switch self { case .notFound: return "Book Not Found" } } } }
-
18:21 - Book Entity with Properties
struct BookEntity: AppEntity, Identifiable { var id: UUID @Property(title: "Title") var title: String @Property(title: "Publishing Date") var datePublished: Date @Property(title: "Read Date") var dateRead: Date? var recommendedBy: String? var displayRepresentation: DisplayRepresentation { "\(title)" } static var typeDisplayRepresentation: TypeDisplayRepresentation = "Book" static var defaultQuery = BookQuery() init(id: UUID) { self.id = id } init(id: UUID, title: String) { self.id = id self.title = title } }
-
20:59 - Books Property Query
struct BookQuery: EntityPropertyQuery { static var sortingOptions = SortingOptions { SortableBy(\BookEntity.$title) SortableBy(\BookEntity.$dateRead) SortableBy(\BookEntity.$datePublished) } static var properties = QueryProperties { Property(\BookEntity.$title) { EqualToComparator { NSPredicate(format: "title = %@", $0) } ContainsComparator { NSPredicate(format: "title CONTAINS %@", $0) } } Property(\BookEntity.$datePublished) { LessThanComparator { NSPredicate(format: "datePublished < %@", $0 as NSDate) } GreaterThanComparator { NSPredicate(format: "datePublished > %@", $0 as NSDate) } } Property(\BookEntity.$dateRead) { LessThanComparator { NSPredicate(format: "dateRead < %@", $0 as NSDate) } GreaterThanComparator { NSPredicate(format: "dateRead > %@", $0 as NSDate) } } } func entities(for identifiers: [UUID]) async throws -> [BookEntity] { identifiers.compactMap { identifier in Database.shared.book(for: identifier) } } func suggestedEntities() async throws -> [BookEntity] { Model.shared.library.books.map { BookEntity(id: $0.id, title: $0.title) } } func entities(matching string: String) async throws -> [BookEntity] { Database.shared.books.filter { book in book.title.lowercased().contains(string.lowercased()) } } func entities( matching comparators: [NSPredicate], mode: ComparatorMode, sortedBy: [Sort<BookEntity>], limit: Int? ) async throws -> [BookEntity] { Database.shared.findBooks(matching: comparators, matchAll: mode == .and, sorts: sortedBy.map { (keyPath: $0.by, ascending: $0.order == .ascending) }) } }
-
24:10 - Dialog
struct AddBook: AppIntent { static var title: LocalizedStringResource = "Add Book" @Parameter(title: "Title") var title: String @Parameter(title: "Author Name") var authorName: String? @Parameter(title: "Recommended By") var recommendedBy: String? func perform() async throws -> some IntentResult & ReturnsValue<BookEntity> & ProvidesDialog { guard var book = await BooksAPI.shared.findBooks(named: title, author: authorName).first else { throw Error.notFound } book.recommendedBy = recommendedBy Database.shared.add(book: book) return .result( value: book, dialog:"Added \(book) to Library!" ) } enum Error: Swift.Error, CustomLocalizedStringResourceConvertible { case notFound var localizedStringResource: LocalizedStringResource { switch self { case .notFound: return "Book Not Found" } } } }
-
24:25 - Snippet
struct AddBook: AppIntent { static var title: LocalizedStringResource = "Add Book" @Parameter(title: "Title") var title: String @Parameter(title: "Author Name") var authorName: String? @Parameter(title: "Recommended By") var recommendedBy: String? func perform() async throws -> some IntentResult & ShowsSnippetView { guard var book = await BooksAPI.shared.findBooks(named: title, author: authorName).first else { throw Error.notFound } book.recommendedBy = recommendedBy Database.shared.add(book: book) return .result(value: book) { CoverView(book: book) } } enum Error: Swift.Error, CustomLocalizedStringResourceConvertible { case notFound var localizedStringResource: LocalizedStringResource { switch self { case .notFound: return "Book Not Found" } } } }
-
24:50 - Request Value
struct AddBook: AppIntent { static var title: LocalizedStringResource = "Add Book" @Parameter(title: "Title") var title: String @Parameter(title: "Author Name") var authorName: String? @Parameter(title: "Recommended By") var recommendedBy: String? func perform() async throws -> some IntentResult { let books = await BooksAPI.shared.findBooks(named: title, author: authorName) guard !books.isEmpty else { throw Error.notFound } if books.count > 1 && authorName == nil { throw $authorName.requestValue("Who wrote the book?") } return .result() } enum Error: Swift.Error, CustomLocalizedStringResourceConvertible { case notFound var localizedStringResource: LocalizedStringResource { switch self { case .notFound: return "Book Not Found" } } } }
-
25:22 - Request Disambiguation
struct AddBook: AppIntent { static var title: LocalizedStringResource = "Add Book" @Parameter(title: "Title") var title: String @Parameter(title: "Author Name") var authorName: String? @Parameter(title: "Recommended By") var recommendedBy: String? func perform() async throws -> some IntentResult { let books = await BooksAPI.shared.findBooks(named: title, author: authorName) guard !books.isEmpty else { throw Error.notFound } if books.count > 1 { let chosenAuthor = try await $authorName.requestDisambiguation(among: books.map { $0.authorName }, dialog: "Which author?") } return .result() } enum Error: Swift.Error, CustomLocalizedStringResourceConvertible { case notFound var localizedStringResource: LocalizedStringResource { switch self { case .notFound: return "Book Not Found" } } } }
-
25:48 - Request Parameter Confirmation
struct AddBook: AppIntent { static var title: LocalizedStringResource = "Add Book" @Parameter(title: "Title") var title: String @Parameter(title: "Author Name") var authorName: String? @Parameter(title: "Recommended By") var recommendedBy: String? func perform() async throws -> some IntentResult & ReturnsValue<BookEntity> { guard var book = await BooksAPI.shared.findBooks(named: title, author: authorName).first else { throw Error.notFound } let confirmed = try await $title.requestConfirmation(for: book.title, dialog: "Did you mean \(book)?") book.recommendedBy = recommendedBy Database.shared.add(book: book) return .result(value: book) } enum Error: Swift.Error, CustomLocalizedStringResourceConvertible { case notFound var localizedStringResource: LocalizedStringResource { switch self { case .notFound: return "Book Not Found" } } } }
-
26:26 - Request Result Confirmation
struct BuyBook: AppIntent { @Parameter(title: "Book") var book: BookEntity @Parameter(title: "Count") var count: Int static var title: LocalizedStringResource = "Buy Book" func perform() async throws -> some IntentResult & ShowsSnippetView & ProvidesDialog { let order = OrderEntity(book: book, count: count) try await requestConfirmation(output: .result(value: order, dialog: "Are you ready to order?") { OrderPreview(order: order) }) return .result(value: order, dialog: "Thank you for your order!") { OrderConfirmation(order: order) } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。