跳到主内容
快讯直播
AI智模界
教程

把 App 接进 Siri:App Intents 实战教程

这篇能做出什么

做完这篇教程,你的 App 会具备三种「被系统调用」的能力:

1. 用户在 快捷指令(Shortcuts) App 里能搜到你的动作,可以拖进自动化流程;

2. 用户对着 Siri 说一句「用 XX 记一条待办」,Siri 直接调用你 App 里的代码,把数据写进去,再回一句话确认,全程不需要打开 App;

3. 在支持 Apple 智能的设备上,系统能按语义理解用户意图,把「帮我记一下明天下午三点开会」这类自然语言路由到你声明的动作上。

举个具体例子:一个叫「清单盒子」的待办 App,做完之后用户可以说「用清单盒子加个任务」,Siri 追问「内容是什么」,用户说「买牛奶」,App 在后台写入「收件箱」清单,Siri 回答「已添加到收件箱」。整个链路的核心就是三个东西:App Intent(动作)、App Entity(可选参数对象)、App Shortcuts(Siri 短语)。

前置条件清单

动手前先对照一遍:

  • 一台 macOS,装有 Xcode(版本以官方文档当前版本为准,App Intents 需要 Xcode 14 及以上);
  • 一个能编译运行的 iOS App 工程,部署目标至少 iOS 16(App Intents 的起点;Apple 智能相关能力需要更高系统版本,以官方文档为准);
  • 一台真机(调试 Siri 语音强烈建议真机,模拟器上 Siri 行为受限,但用快捷指令 App 触发动作是可行的);
  • Apple Developer 账号(真机调试需要签名);
  • 设备上已开启 Siri,并在「设置 → Siri 与搜索」里确认语言与你的短语一致;
  • 心里想清楚一个具体、边界清晰的动作,比如「添加一条待办」「查询今天还剩几件事」。不要一上来就做「万能助手」,那是调不通的。

第一步:写一个最小可用的 App Intent

先别管 Siri,先让动作本身能跑起来。新建一个 Swift 文件,比如 AddTaskIntent.swift:

```swift

import AppIntents

struct AddTaskIntent: AppIntent {

// Siri 和快捷指令里显示的名字,越短越好

static var title: LocalizedStringResource = "添加待办"

// 给系统理解用的描述,写清楚"做什么"

static var description = IntentDescription("把一条待办事项加入指定清单")

// 这个动作不需要打开 App,在后台完成

static var openAppWhenRun: Bool = false

@Parameter(title: "内容")

var text: String

@Parameter(title: "清单")

var listName: String?

func perform() async throws -> some IntentResult & ProvidesDialog {

let target = listName ?? "收件箱"

try TaskStore.shared.add(text: text, to: target)

return .result(dialog: "已添加到\(target)")

}

}

```

几个必须知道的点:

  • @Parameter 修饰的属性必须是 var。非可选参数如果没有默认值,系统会在运行时追问用户;可选参数(String?)则可以跳过。
  • perform() 是 async throws 的,耗时操作放这里没问题,但要控制时长,见后面「常见坑」。
  • 返回 .result(dialog:) 才能让 Siri 说出确认语;只返回 .result() 时 Siri 可能只回一句通用的「完成了」。
  • openAppWhenRun 默认是 false。如果你需要用户看到界面再操作,把它设成 true。

第二步:注册成 Siri 听得懂的短语

Intent 写完了,系统还不知道该在什么话术下调用它。这就要靠 AppShortcutsProvider:

```swift

import AppIntents

struct TaskAppShortcuts: AppShortcutsProvider {

static var appShortcuts: [AppShortcut] {

AppShortcut(

intent: AddTaskIntent(),

phrases: [

"用 \(.applicationName) 记一条待办",

"在 \(.applicationName) 里加个任务",

"让 \(.applicationName) 帮我记一下"

],

shortTitle: "记待办",

systemImage: "checklist"

)

}

}

```

规矩很硬,踩了就不生效:

  • 每条短语必须包含 \(.applicationName)。这是系统用来识别「这句话属于哪个 App」的锚点,缺了直接编译报错。
  • 一条 AppShortcut 至少一句短语,建议 2~4 句,覆盖不同说法。
  • 短语要以动词开头、口语化、短。像「用 XX 添加一条待办事项到清单」这种书面长句,识别率会明显下降。
  • 每个 App 能注册的 App Shortcut 数量有上限(官方文档写明,数量不多),所以优先挑真正高频的动作。
  • 这个 struct 放在主 App target 里,不要放进扩展。

第三步:让参数可挑选(AppEntity + EntityQuery)

上面 listName 是裸字符串,用户得自己念清单名,很容易念错。更好的做法是把「清单」定义成一个实体:

```swift

import AppIntents

struct TaskListEntity: AppEntity {

static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "清单")

static var defaultQuery = TaskListQuery()

var id: String

var name: String

var displayRepresentation: DisplayRepresentation {

DisplayRepresentation(title: "\(name)")

}

}

struct TaskListQuery: EntityQuery {

// 系统拿着已有 id 回查实体

func entities(for identifiers: [String]) async throws -> [TaskListEntity] {

identifiers.compactMap { TaskStore.shared.list(withID: $0) }

}

// Siri 追问"哪个清单"时给出的候选

func suggestedEntities() async throws -> [TaskListEntity] {

TaskStore.shared.allLists()

}

// 用户没指定时的默认值

func defaultResult() async -> TaskListEntity? {

TaskStore.shared.allLists().first

}

}

```

然后把参数改成:

```swift

@Parameter(title: "清单")

var list: TaskListEntity?

```

这样 Siri 会读出候选清单让用户选,而不是靠语音识别猜字符串。同理,如果有「优先级」这种固定选项,用 AppEnum 比用字符串靠谱得多——系统能直接理解 AppEnum 的取值,语义路由也更准。

第四步:暴露给 Apple 智能与系统域

在支持 Apple 智能的系统上,除了照短语匹配,系统还会按语义理解来路由请求。要吃到这波能力,需要做两件事:

一是把动作描述清楚。 系统看不懂你的业务黑话,只能看你给的元数据。检查清单:

  • title 用动词短语,别用名词(「添加待办」而不是「待办事项」);
  • description 一句话讲清楚输入输出;
  • 参数全部用系统能理解的类型:基础类型、AppEnum、AppEntity,不要用自定义 struct;
  • 返回 dialog,让系统知道动作成功了。

二是声明所属的系统域(domain)。 系统为相册、邮件、笔记等常见场景预定义了一批 schema,实现对应的协议能让你的动作被更高层的能力直接调度:

```swift

import AppIntents

// schema 名称在 Xcode 里输入 "." 会触发补全,

// 具体可用列表以官方文档的 App Intent domains 页面为准

@AssistantIntent(schema: .yourDomain.yourAction)

struct OpenTaskIntent: AppIntent {

static var title: LocalizedStringResource = "打开待办"

@Parameter(title: "待办")

var task: TaskEntity

func perform() async throws -> some IntentResult {

AppRouter.shared.openTask(id: task.id)

return .result()

}

}

```

如果暂时没有匹配到你业务的 schema,也不必强求——把普通的 AppIntent + AppShortcut 做好,Siri 和快捷指令的可用性已经足够覆盖大部分场景。schema 的具体命名与可用范围请以官方文档为准,不同系统版本差异较大。

第五步:处理权限与运行时

App Intents 本身不需要额外申请权限,但动作里调用的系统能力需要。这是新手最容易漏的一环:快捷指令里跑得好好的,一装到别人手机上就返回失败。

  • 涉及通讯录、相册、日历、提醒事项等,先在 Info.plist 里写好对应的 usage description;
  • 在 perform() 里做一次状态检查,没授权就抛出带说明的错误:

```swift

func perform() async throws -> some IntentResult & ProvidesDialog {

let status = await ContactsStore.authorizationStatus()

guard status == .authorized else {

throw IntentError.permissionDenied

}

// ...

return .result(dialog: "已保存")

}

```

自定义错误建议实现 LocalizedError,errorDescription 写成人话(「需要通讯录权限,请在设置中开启」),否则用户只会看到一句「出错了」。

  • 如果动作需要展示界面,就设 static var openAppWhenRun = true,并把 UI 相关代码标 @MainActor。

第六步:调试与验证

调试顺序建议从「最可控」到「最真实」:

1)用单元测试直接调 perform()。 这能最快验证业务逻辑,绕开所有系统层:

```swift

import XCTest

@testable import TaskApp

final class AddTaskIntentTests: XCTestCase {

func testAddTask() async throws {

let intent = AddTaskIntent()

intent.text = "买牛奶"

intent.list = TaskListEntity(id: "shopping", name: "购物")

let result = try await intent.perform()

XCTAssertTrue(TaskStore.shared.list(withID: "shopping")?.items.contains("买牛奶") ?? false)

}

}

```

2)在快捷指令 App 里手动触发。 装好 App 并至少启动一次,打开「快捷指令」→ 搜索你的 App 名字,应该能看到动作卡片。拖一个出来点运行,这一步能验证 Intent 注册、参数解析、返回值是否正常。

3)用 Siri 语音触发。 真机上直接念短语。如果听错,把短语改短、换更常见的动词。

4)下断点。 在 Xcode 里先把 App 跑起来(或用 Debug → Attach to Process 附着到已安装的 App),再从快捷指令触发动作,断点就能命中 perform()。

5)看日志。 用 OSLog 打点,在 Console.app 里按 subsystem 过滤:

```swift

import OSLog

let logger = Logger(subsystem: "com.example.taskapp", category: "intents")

func perform() async throws -> some IntentResult & ProvidesDialog {

logger.info("AddTaskIntent 收到参数 text=\(text, privacy: .public)")

// ...

}

```

常见坑与排错

短语写好了但 Siri 完全没反应。

先确认短语里有没有 \(.applicationName)(没有的话根本编译不过);再确认 App 真的装到了这台设备上,并且启动过一次。还有一个高频原因:App Shortcuts 会被系统缓存。改了短语之后,删掉 App 重新安装一次,通常就能刷新。

快捷指令里搜不到我的动作。

检查 AppShortcutsProvider 是否放在主 target、appShortcuts 是不是 static var 返回数组。另外,只有被 AppShortcut 引用过的 Intent 才会自动出现在快捷指令库里;没有注册的 Intent 需要用户手动「新建快捷指令 → 搜索 App」。

Siri 说「抱歉,我没听懂」。

多半是短语太书面或太长。改成「用 X 记一下」这类 5~8 个字的口语短句;同时确认 Siri 语言和你的短语语言一致(中英文混着说很容易失败)。

参数选不出来,或者一运行就报参数错误。

自定义类型没有实现 AppEntity + EntityQuery,或者忘了给 defaultQuery。系统拿不到候选列表,只能报错。

Siri 回了「已完成」,但数据没写进去。

perform() 可能抛了错但被吞掉了。检查有没有实现 LocalizedError,把错误信息透出来;同时确认 App 是不是在被杀死状态下被唤起——这时候单例、数据库连接可能还没初始化好,需要在 perform() 里做懒加载。

动作跑到一半超时。

perform() 适合秒级完成的工作。上传大文件、批量同步这类操作,正确做法是立刻返回 .result(dialog: "已开始同步"),把重活丢到后台任务或本地通知,完成后再告知用户。

模拟器上 Siri 不好使。

这属于正常现象。模拟器上优先用快捷指令 App 触发同一个 Intent 来验证逻辑,语音环节留给真机。

改了代码但行为没变。

除了删 App 重装,也要注意 Xcode 的增量安装有时不会刷新 Intent 元数据。遇到玄学问题,先 Clean Build Folder,再卸载重装。

下一步建议

链路跑通之后,可以按这个顺序继续加厚:

1. 加 SiriTipView,在 App 首页把常用短语提示给用户,比任何新手引导都有效;

2. 做参数追问体验:给 @Parameter 加上追问文案,让 Siri 的问法更像人话;

3. 接入更多系统入口:同一批 App Intent 可以同时供小组件、控制中心控件、操作按钮使用,写一次多处复用;

4. 做 Spotlight 索引:让实体能被系统搜索到,语义路由的命中率会明显提升(相关协议以官方文档为准);

5. 补本地化:短语和 title 都在 String Catalog 里翻译,否则换语言环境就废了;

6. 观察线上表现:给 Intent 的入口、成功、失败分别打点,跑一段时间再回来优化短语和参数设计。

最后提醒一句:App Intents 的可用能力与系统版本强绑定,本文涉及的 API 名称、数量上限、schema 列表都可能随版本演进,动手时请以官方文档当前版本为准。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。