WWDC Quick Look 💓 By SwiftGGTeam
Bring your app's core features to users with App Intents

Bring your app's core features to users with App Intents

观看原视频

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 定义的 TrailEntityTrailEntityQuery,在 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 协议,实现 displayRepresentationidEnumerableEntityQuery

  • 做什么:把最高频操作做成 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

评论

GitHub Issues · utterances