WWDC Quick Look 💓 By SwiftGGTeam
Implement App Shortcuts with App Intents

Implement App Shortcuts with App Intents

观看原视频

Highlight

iOS 16 的 App Intents 框架让开发者可以用纯 Swift 代码定义 App Shortcuts,无需额外的 metadata 文件,安装后自动在 Siri、Spotlight 和 Shortcuts 应用中可用。

核心内容

过去,用户需要点击”Add to Siri”按钮或在 Shortcuts 应用中手动设置,才能使用你的 Intent。App Shortcuts 消除了这个门槛——应用安装后,Intent 立即在 Siri、Spotlight 和 Shortcuts 应用中可用。(01:02

App Intents 是一个全新的 Swift-only 框架,所有定义都在 Swift 源代码中完成,不需要单独的 metadata 文件。(02:57)这带来了几个好处:没有代码生成步骤、不用在源代码编辑器和 metadata 编辑器之间切换、代码审查更方便、合并冲突更容易解决。

以一个冥想应用为例。以前用户要打开应用、登录、找到想听的冥想 session。有了 App Shortcuts,用户直接说”开始冥想”就能启动默认 session。(02:13

详细内容

基础 App Intent

Intent 是一个实现 AppIntent 协议的 Swift struct,只需要两个东西:标题和 perform 方法。(03:44

import AppIntents

struct StartMeditationIntent: AppIntent {
    static let title: LocalizedStringResource = "Start Meditation Session"

    func perform() async throws -> some IntentResult & ProvidesDialog {
        await MeditationService.startDefaultSession()
        return .result(dialog: "Okay, starting a meditation session.")
    }
}

关键点:

  • title 用于在 Shortcuts 应用中显示这个 Intent
  • perform() 是 async 的,可以执行异步操作
  • 返回 .result(dialog:) 向用户展示反馈
  • 如果应用做了本地化,dialog 字符串也需要本地化

创建 App Shortcut

有了 Intent 还不够,需要把它包装成 App Shortcut。实现 AppShortcutsProvider 协议:(05:31

import AppIntents

struct MeditationShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: StartMeditationIntent(),
            phrases: [
                "Start a \(.applicationName)",
                "Begin \(.applicationName)",
                "Meditate with \(.applicationName)",
                "Start a session with \(.applicationName)"
            ]
        )
    }
}

关键点:

  • \(.applicationName) 代替硬编码应用名,Siri 会自动插入主应用名和已配置的同义词
  • 提供多个短语变体,覆盖用户可能的不同说法
  • 每个应用最多 10 个 App Shortcut,但大多数应用只需要几个
  • 短语需要本地化到应用支持的所有语言

自定义 Snippet 视图

默认情况下,Siri 运行 Intent 时显示一个通用视图。你可以返回自定义的 SwiftUI 视图,让它更有信息量。(07:40

func perform() async throws -> some ProvidesDialog & ShowsSnippetView {
    await MeditationService.startDefaultSession()

    return .result(
        dialog: "Okay, starting a meditation session.",
        view: MeditationSnippetView()
    )
}

关键点:

  • 自定义视图使用 SwiftUI,和 Widget 使用同样的视图技术
  • 不需要单独创建 UI extension
  • 视图不能包含交互或动画(和 Widget 一样的限制)
  • 可以在三个阶段展示自定义 UI:值确认、Intent 确认、Intent 完成后

添加参数

让用户指定具体的冥想 session 类型,需要添加参数。首先定义参数类型,实现 AppEntity 协议:(10:09

import AppIntents

struct MeditationSession: AppEntity {
    let id: UUID
    let name: LocalizedStringResource

    static var typeDisplayName: LocalizedStringResource = "Meditation Session"
    var displayRepresentation: AppIntents.DisplayRepresentation {
        DisplayRepresentation(title: name)
    }

    static var defaultQuery = MeditationSessionQuery()
}

然后实现查询协议,让 App Intents 框架能根据标识符查找实体:(10:55

struct MeditationSessionQuery: EntityQuery {
    func entities(for identifiers: [UUID]) async throws -> [MeditationSession] {
        return identifiers.compactMap { SessionManager.session(for: $0) }
    }
}

在 Intent 中添加参数:(11:16

struct StartMeditationIntent: AppIntent {
    @Parameter(title: "Session Type")
    var sessionType: SessionType?

    // ...
}

提示用户选择值

如果用户没有提供参数值,可以主动提示。App Intents 支持三种提示类型:歧义消除(从固定列表选)、值提示(开放式值)、确认(验证特定值)。(12:14

func perform() async throws -> some ProvidesDialog {
    let sessionToRun = self.session ?? try await $session.requestDisambiguation(
        among: SessionManager.allSessions,
        dialog: IntentDialog("What session would you like?")
    )

    await MeditationService.start(session: sessionToRun)
    return .result(
        dialog: "Okay, starting a \(sessionToRun.name) meditation session."
    )
}

关键点:

  • 使用 $session.requestDisambiguation 从固定列表中让用户选择
  • App Intents 会用每个 session 的 displayRepresentation 格式化列表项
  • 用户选择后,选中的项会返回给 perform 方法
  • 提示会拖慢交互,不要过度使用

参数化短语

让用户在初始指令中直接指定参数值,比如”开始一个平静冥想”。这需要三个步骤:(15:27

第一步,在 Query 中实现 suggestedEntities()

struct MeditationSessionQuery: EntityQuery {
    func entities(for identifiers: [UUID]) async throws -> [MeditationSession] {
        return identifiers.compactMap { SessionManager.session(for: $0) }
    }

    func suggestedEntities() async throws -> [MeditationSession] {
        return SessionManager.allSessions
    }
}

关键点:这个方法必须实现,否则不会生成参数化短语。

第二步,在模型层通知 App Intents 参数值变化:(16:34

class SessionModel {
    @Published
    var sessions: [MeditationSession] = []
    private var cancellable: AnyCancellable?

    init() {
        self.cancellable = $sessions.sink { _ in
            MeditationShortcuts.updateAppShortcutParameters()
        }
    }
}

关键点:

  • updateAppShortcutParameters() 是 App Intents 框架提供的方法
  • 调用后,框架会重新查询实体列表,更新参数化短语
  • 通常在从服务器获取新 session 后调用

第三步,在 App Shortcut 中添加带参数的短语:(17:09

struct MeditationShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: StartMeditationIntent(),
            phrases: [
                "Start a \(.applicationName)",
                "Begin \(.applicationName)",
                "Meditate with \(.applicationName)",
                "Start a \($session) session with \(.applicationName)",
                "Begin a \($session) session with \(.applicationName)",
                "Meditate on \($session) with \(.applicationName)"
            ]
        )
    }
}

关键点:

  • \($session) 引用 Intent 中的参数
  • 框架会把参数值列表与短语组合,生成所有变体
  • 参数值的显示文本来自 DisplayRepresentationtitle

可发现性

App Shortcuts 安装即可用,但用户需要知道它们存在。(17:55

  • Siri Tip:在应用内合适时机展示,SwiftUI 和 UIKit 都支持,有多种样式
  • ShortcutsLink:跳转到 Shortcuts 应用中该应用的所有 shortcut 列表
  • Siri 主动推荐:用户问”What can I do here?”时自动推荐

注意事项

App Shortcuts 在应用首次启动前就可能被运行。(20:36)如果应用需要登录,Intent 应该优雅地失败,提示用户需要先登录。参数化短语在应用首次启动并通知框架前不可用,所以建议至少保留几个非参数化短语。

核心启发

1. 为高频操作添加零配置的语音入口

找出应用中用户每天会做的操作,用 App Shortcuts 提供一句话触达。比如健身应用的”开始跑步”、记账应用的”记录一笔支出”。实现成本很低——一个 Intent struct 和一个 AppShortcutsProvider 就能让功能在 Siri 和 Spotlight 中可用。

2. 用参数化短语减少交互步骤

如果你的应用有固定类别的内容(比如播放列表、收藏餐厅、常用房间),把类别名做成参数,让用户说”播放我的跑步歌单”而不是”播放音乐”然后被追问”播放什么”。实现 suggestedEntities() 并在数据变化时调用 updateAppShortcutParameters() 即可。

3. 在应用内 strategically 展示 Siri Tip

不要随机弹窗。选择用户完成某个操作后可能想下次快速重复的时刻,比如刚手动下单后展示”下次可以说’查看我的订单‘“的 tip。SwiftUI 中直接用 SiriTipView,UIKit 用对应的视图。支持关闭回调,记录用户偏好避免重复打扰。

4. 为未登录状态设计优雅降级

App Shortcuts 可能在用户从未打开过应用时就被触发。在 perform() 中检查必要状态(如登录),如果条件不满足,返回一个友好的 dialog 引导用户打开应用完成设置,而不是崩溃或静默失败。

关联 Session

评论

GitHub Issues · utterances