WWDC Quick Look 💓 By SwiftGGTeam
Build intelligent Siri experiences with App Schemas

Build intelligent Siri experiences with App Schemas

观看原视频

Highlight

Siri 在 iOS 27 中通过 App Schemas 获得了理解 App 内容、执行跨 App 操作和感知屏幕上下文的能力。开发者只需将现有数据建模为 AppEntity、接入系统预定义的 Schema 并标注视图,即可让 Siri 用自然语言搜索、操作和流转 App 内容。

核心内容

以前让 Siri 控制 App,开发者得自己处理自然语言解析,写一堆自定义 Intent,用户还得记住固定说法。体验生硬,覆盖场景有限。

今年 Apple Intelligence 让 Siri 变成了真正能”看懂”App 的助手。它不再把 App 当黑盒,而是直接理解 App 里的实体(Entity)是什么、能做什么动作、屏幕上显示了什么。开发者不需要自己处理语言理解,只需要描述清楚 App 的数据结构和能力,Siri 负责把用户的话翻译成具体操作。

这个转变的核心是 App Schemas。App Schemas 是系统预定义的概念集合,比如消息、联系人、文档。当开发者的 AppEntity 遵循某个 Schema 时,Siri 已经知道这个概念的含义、属性和常用操作,不需要从头学习。

举个例子:用户说”给 Glow 发消息问有什么电影推荐”。Siri 知道”发消息”对应 messages domain 的 sendMessage schema,知道”Glow”是一个联系人实体,知道”有什么电影推荐”是消息内容。它自动完成实体解析、参数填充和意图执行,App 只负责真正发送消息。

更厉害的是跨 App 操作。用户可以说”把这条回复邮件发给我老婆”,Siri 先理解屏幕上”这条回复”指的是哪个消息实体,再把它导出为系统能理解的格式,传给邮件 App 执行发送。整个流程涉及屏幕感知、内容导出、跨 App 流转三个环节,开发者只需要标注视图和实现 Transferable 协议。

Siri 的三项新能力

理解 App 内容01:57):Siri 现在能直接访问 App 的实体。用户问”我下一场会议在什么时候、在哪里”,Siri 能识别出”会议”这个实体类型,找到最相关的那一个,返回时间和地点属性。

执行 App 动作02:19):用户说”把最新报告发给 Mary”,Siri 解析出”发送”动作、“最新报告”实体和”Mary”收件人,调用 App 的 sendMessage intent 完成操作。

感知屏幕上下文02:44):用户指着屏幕说”解释这段文字”或”查这个产品的评价”,Siri 通过视图标注知道屏幕上显示了什么内容,把”这段文字""这个产品”解析成具体实体。

语义搜索 vs 字符串查询

大部分场景下,IndexedEntity 是首选。它把实体属性索引到系统语义索引中,Siri 能基于含义匹配,而不是逐字匹配。用户说”Flare 上次说关于电影的事”,Siri 能找到提到电影的消息,即使消息里没有”电影”这个词。

但 IndexedEntity 不适合所有情况。数据量大、存在服务端、变化频繁的数据,用 EntityStringQuery 更合适。Siri 把用户输入的字符串交给 App,App 自己决定怎么搜索和匹配。代价是失去了语义理解能力,但获得了完全的控制权。

屏幕感知的两种 API

一种情况是屏幕上只有一个主要内容,比如看文档、写邮件。用 NSUserActivity 标记当前活动,Siri 知道”这个”指的是什么。

另一种情况是屏幕上同时显示多个内容项,比如聊天列表、商品列表。用 .appEntityIdentifier 修饰符给每个列表项绑定对应的实体标识。Siri 能区分”这条消息”和”那条消息”,支持”转发最后一条”这种指令。

内容流转的两种模式

内容从其他 App 进入你的 App 时,有两种处理方式。如果内容对应 App 里已有的数据,用 IntentValueQuery 把它解析成现有实体。如果内容应该创建新数据,用 Transferable 的 importing 闭包把传入值转换成新实体。很多 App 两种都用,取决于具体场景。

详细内容

用 AppEntity 建模数据

AppEntity 用描代替新数据模型,述现有数据的方式。它回答三个问题:这个东西是什么、怎么识别它、哪些属性重要。

以 UnicornChat 为例,App 里有 Contact(联系人)、Conversation(对话)、Message(消息)三种实体。每种都遵循对应的 App Schema:

@AppEntity(schema: .messages.message)
struct MessageEntity: IndexedEntity {

    // 告诉 Spotlight 哪些属性参与语义搜索
    @Property(indexingKey: \.textContent)
    var body: AttributedString?
}

关键点:

  • @AppEntity(schema: .messages.message) 把 MessageEntity 绑定到 messages domain 的 message schema,Siri 立刻知道这是”消息”概念
  • IndexedEntity 协议让系统为 body 属性建立语义索引
  • indexingKey 指定参与语义搜索的属性,Siri 能基于含义而非字面匹配找到内容

字符串查询兜底

当数据无法索引时,实现 EntityStringQuery:

struct ContactQuery: EntityStringQuery {
    func entities(matching string: String) async throws -> [ContactEntity] {
        let predicate = #Predicate<Person> { person in
            person.name.localizedStandardContains(string)
        }
        let descriptor = FetchDescriptor<Person>(predicate: predicate)
        let matches = try modelContext.fetch(descriptor)
        return matches.map(\.entity)
    }
}

关键点:

  • EntityStringQuery 协议要求实现 entities(matching:) 方法
  • Siri 把用户的自然语言输入原样传入,App 负责查找匹配实体
  • 示例中使用 SwiftData 的 FetchDescriptor#Predicate 做本地查询
  • 返回 [ContactEntity] 让 Siri 继续后续操作

标注屏幕内容

让 Siri 理解列表中每个条目对应哪个实体:

List {
    ForEach(messages) { message in
        MessageRow(message: message)
            .appEntityIdentifier(
                EntityIdentifier(
                    for: MessageEntity.self,
                    identifier: message.id
                )
            )
    }
}

关键点:

  • .appEntityIdentifier 是 SwiftUI 视图修饰符,把 UI 元素和 AppEntity 关联
  • EntityIdentifier 需要指定实体类型和唯一标识符
  • 用户说”编辑这条消息”时,Siri 从视图标注中解析出具体是哪个 MessageEntity
  • 只有当前渲染在屏幕上的列表项会被标注,LazyVStack 中屏幕外的项 Siri 感知不到

导出实体供其他 App 使用

让其他 App 能操作你的内容:

extension ContactEntity: Transferable {

    static var transferRepresentation: some TransferRepresentation {
        IntentValueRepresentation(
            exporting: \.person
        )
    }
}

关键点:

  • Transferable 协议让实体可以在 App 间流转
  • IntentValueRepresentation 定义导出格式,这里把 ContactEntity 导出为系统标准的 IntentPerson
  • 导出后,其他 App 的 Intent 可以接收这个联系人,比如”给这个联系人打电话”

导入内容创建新实体

接收其他 App 传来的内容并创建新数据:

extension ContactEntity: Transferable {

    static var transferRepresentation: some TransferRepresentation {
        IntentValueRepresentation(
            exporting: \.person,
            importing: { intentPerson in
                let contact = Contact(importing: intentPerson)
                ContactManager.shared.contacts.append(contact)
                return contact.entity
            }
        )
    }
}

关键点:

  • importing 闭包在收到外部内容时执行
  • 把系统标准的 IntentPerson 转换成 App 内部的 Contact 模型
  • 添加到 ContactManager 后返回对应的 ContactEntity
  • 注意线程安全:如果 ContactManager 绑定 MainActor,importing 闭包中需要正确处理 Actor 隔离

解析传入的 Intent 值

当其他 App 传来的内容应该匹配已有数据时:

struct ContactEntityQuery: IntentValueQuery {

    func values(for input: [IntentPerson]) async throws -> [ContactEntity] {
        let names = input.map(\.displayName)
        let descriptor = FetchDescriptor<Contact>()
        let contacts = try model.mainContext.fetch(descriptor)
        let matches = contacts.filter { contact in
            names.contains(where: { name in
                contact.name.localizedStandardContains(name)
            })
        }
        return matches.map(\.entity)
    }
}

关键点:

  • IntentValueQuery 协议用于把传入的系统值解析成 App 实体
  • 示例中接收 [IntentPerson],按 displayName 匹配本地 Contact
  • 使用 localizedStandardContains 做本地化模糊匹配
  • 返回解析后的 [ContactEntity] 供 Intent 使用

Xcode Fix-It 补全 Schema

Xcode 会检查 Schema 的完整性。如果实现了 sendMessage,但没实现相关的 draftMessage,编译时报错并生成补全代码:

// Xcode Fix-It 生成的 draftMessage 骨架
@main
struct DraftMessageIntent: AppIntent {
    static var title: LocalizedStringResource = "Draft Message"

    @Parameter
    var recipient: ContactEntity?

    @Parameter
    var content: String?

    @MainActor
    func perform() async throws -> some IntentResult {
        // 打开消息创建界面
        return .result()
    }
}

关键点:

  • 某些 Siri 场景需要多个相关 Schema 配合才能完成完整体验
  • Xcode 在编译期暴露缺失的 Schema,而不是运行时静默失败
  • Fix-It 生成完整的 Intent 定义、参数声明和骨架实现
  • 开发者只需填充 App 特定的业务逻辑

核心启发

做一个 Siri 能”看懂”的笔记 App

做什么:让用户用自然语言搜索笔记,比如”找上周关于预算的笔记”,Siri 基于语义索引返回结果。

为什么值得做:IndexedEntity 让本地笔记内容进入系统语义索引,用户不需要记住笔记标题或标签。

怎么开始:把 Note 模型包装成遵循 AppEntity(schema: .notes.note) 的实体,给 contenttitle 属性加 indexingKey,实现 EntityStringQuery 作为云端笔记的兜底方案。

做一个支持跨 App 流转的待办 App

做什么:用户在邮件 App 里看到任务,说”把这个加到待办列表”,Siri 把邮件内容解析成待办事项。

为什么值得做:Transferable + importing 让其他 App 的内容无缝流入你的 App,用户不需要手动复制粘贴。

怎么开始:让 TodoEntity 遵循 Transferable,实现 IntentValueRepresentation 的 importing 闭包,把传入的文本或 URL 转换成 Todo 对象。

做一个屏幕感知的图片编辑 App

做什么:用户选中一张图片说”给这张加个电影滤镜”,Siri 知道”这张”指的是当前屏幕上选中的图片。

为什么值得做:.appEntityIdentifier 让 Siri 精确识别屏幕上的内容,支持”这个""那个”这种自然指代。

怎么开始:给图片网格中的每个 Image 视图加 .appEntityIdentifier,绑定到 PhotoEntity,实现 applyFilter 的 AppIntent。

做一个 Schema 完整的邮件客户端

做什么:完整接入 messages domain,支持发送、草稿、搜索等全套 Siri 操作。

为什么值得做:Xcode 会引导你补全整个 domain 的所有 Schema,实现后 Siri 对邮件的操作体验与系统原生邮件一致。

怎么开始:从 sendMessage schema 开始,Xcode Fix-It 会提示缺少哪些相关 Schema,逐个实现即可。

关联 Session

评论

GitHub Issues · utterances