关于 app-intents
用于实现和审查 iOS App Intents 的开发技能,帮助将应用操作和实体提供给 Siri、Shortcuts、Spotlight、小组件、控制中心及 Apple Intelligence,并涵盖 iOS 26 新增 API。
主要功能
- 01AppIntent 与参数设计
- 02AppEntity 与实体查询
- 03AppEnum 与快捷指令短语
- 04系统界面集成指引
- 05iOS 26 交互片段与视觉查询
- 06实现审查清单
适用场景
- 01让 Siri 和 Shortcuts 调用应用操作
- 02为 Spotlight 搜索提供应用实体
- 03配置小组件或控制中心操作
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill app-intents安装后在 Claude Code 中输入/app-intents确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/appintents-advanced.mdsystem-surfaces.md详细介绍
App Intents(iOS 26+)
实现、审查和扩展 App Intents,将应用功能开放给 Siri、 Shortcuts、Spotlight、小组件、控制中心和 Apple Intelligence。
目录
- 分类处理工作流
- AppIntent 协议
@Parameter- AppEntity
- EntityQuery(4 种变体)
- AppEnum
- AppShortcutsProvider
- 系统界面集成
- iOS 26 新增内容
- 常见错误
- 审查清单
- 参考资料
分类处理工作流
第 1 步:选择操作、边界和界面
从用户希望在应用之外使用的 1-3 个有价值的操作入手,而不是从应用的屏幕层级入手。每个操作记录一行设计信息:用户目标;内联结果或应用目的地;共享的领域操作;参数和实体查询;确认/身份验证;目标界面和协议。使用一个明确的运行时路由来处理应用跳转,而不要在各个 intent 中散布导航副作用。
然后,为该操作选择合适的系统功能和协议:
| 界面 | 协议 | 起始版本 |
|---|---|---|
| Siri / Shortcuts | AppIntent | iOS 16 |
| 可配置小组件 | WidgetConfigurationIntent | iOS 17 |
| 控制中心 | ControlConfigurationIntent | iOS 18 |
| Spotlight 搜索 | IndexedEntity | iOS 18 |
| Apple Intelligence | @AppIntent(schema:) | iOS 18 |
| 交互式片段 | SnippetIntent | iOS 26 |
| 视觉智能 | IntentValueQuery | iOS 26 |
第 2 步:定义数据模型
- 优先使用
AppEntity影子模型来向系统公开应用数据。 - 为固定的参数选项创建
AppEnum类型。 - 选择合适的
EntityQuery变体来解析实体。 - 使用
IndexedEntity和indexingKey元数据标记可搜索的实体。
第 3 步:实现 intent
- 遵循
AppIntent(或专用子协议)。 - 为所有面向用户的输入声明
@Parameter属性。 - 实现
perform() async throws -> some IntentResult。 - 添加
parameterSummary以支持 Shortcuts UI。 - 通过
AppShortcutsProvider注册短语。
第 4 步:验证
- 在目标系统界面中构建并运行,确认发现、参数解析、取消、确认、身份验证、结果呈现和应用跳转均正常。
- 如果某一步失败,请重置测试数据,修复最小范围的 intent/实体/查询边界,并从同一界面重新运行同一操作,然后再添加其他操作。
- 使用 Xcode 中的 intent 预览测试 Siri 短语。
- 确认
IndexedEntity实例已编入具名 Spotlight 索引。 - 检查使用
WidgetConfigurationIntent的小组件配置。
AppIntent 协议
系统通过 init() 实例化结构体、设置参数,然后调用
perform()。声明 title 和 parameterSummary 以支持 Shortcuts UI。
struct OrderSoupIntent: AppIntent {
static var title: LocalizedStringResource = "Order Soup"
static var description = IntentDescription("Place a soup order.")
@Parameter(title: "Soup") var soup: SoupEntity
@Parameter(title: "Quantity", default: 1) var quantity: Int
static var parameterSummary: some ParameterSummary {
Summary("Order \(\.$soup)") { \.$quantity }
}
func perform() async throws -> some IntentResult {
try await OrderService.shared.place(soup: soup.id, quantity: quantity)
return .result(dialog: "Ordered \(quantity) \(soup.name).")
}
}
可选成员:description(IntentDescription)、openAppWhenRun(Bool)、
isDiscoverable(Bool)、authenticationPolicy(IntentAuthenticationPolicy)。
@Parameter
使用 @Parameter 声明每个面向用户的输入。非可选参数为必填;系统会在需要时请求相应值。默认值会预先填入实用的值。可选参数不会自动请求,因此如果 intent 无法在缺少该值的情况下继续,请在 perform() 中询问用户。
// Required; the system asks for a value when needed
@Parameter(title: "Count")
var count: Int
// Required and pre-filled
@Parameter(title: "Count", default: 1)
var count: Int
// Optional; request it yourself if it becomes necessary
@Parameter(title: "Count")
var count: Int?
支持的值类型
基本类型:Bool、Int、Double、String、Duration、Date、Decimal、
Measurement 和 URL。集合:元素类型受支持的 Array 和 Set。框架类型:IntentPerson、IntentFile。自定义类型:任何 AppEntity 或
AppEnum。
常见初始化模式
// Basic
@Parameter(title: "Name")
var name: String
// With default
@Parameter(title: "Count", default: 5)
var count: Int
// Numeric slider
@Parameter(title: "Volume", controlStyle: .slider, inclusiveRange: (0, 100))
var volume: Int
// Options provider (dynamic list)
@Parameter(title: "Category", optionsProvider: CategoryOptionsProvider())
var category: Category
// File with content types
@Parameter(title: "Document", supportedContentTypes: [.pdf, .plainText])
var document: IntentFile
// Measurement with unit
@Parameter(title: "Distance", defaultUnit: .miles, supportsNegativeNumbers: false)
var distance: Measurement<UnitLength>
所有初始化器变体请参阅 references/appintents-advanced.md。
AppEntity
优先使用与应用数据相映射、且仅公开面向系统字段的影子模型。 当模型轻量、稳定且适用于 App Intents 生命周期时,也可以直接遵循模型。
struct SoupEntity: AppEntity {
static let defaultQuery = SoupEntityQuery()
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Soup"
var id: String
@Property(title: "Name") var name: String
@Property(title: "Price") var price: Double
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)", subtitle: "$\(String(format: "%.2f", price))")
}
init(from soup: Soup) {
self.id = soup.id; self.name = soup.name; self.price = soup.price
}
}
必需项:id、defaultQuery(静态)、displayRepresentation、
typeDisplayRepresentation(静态)。使用 @Property(title:) 标记属性,
使其可用于筛选和排序。未使用 @Property 的属性仍为内部属性。
EntityQuery(4 种变体)
1. EntityQuery(基础——按 ID 解析)
struct SoupEntityQuery: EntityQuery {
func entities(for identifiers: [String]) async throws -> [SoupEntity] {
SoupStore.shared.soups.filter { identifiers.contains($0.id) }.map { SoupEntity(from: $0) }
}
func suggestedEntities() async throws -> [SoupEntity] {
SoupStore.shared.featured.map { SoupEntity(from: $0) }
}
}
2. EntityStringQuery(自由文本搜索)
struct SoupStringQuery: EntityStringQuery {
func entities(matching string: String) async throws -> [SoupEntity] {
SoupStore.shared.search(string).map { SoupEntity(from: $0) }
}
func entities(for identifiers: [String]) async throws -> [SoupEntity] {
SoupStore.shared.soups.filter { identifiers.contains($0.id) }.map { SoupEntity(from: $0) }
}
}
3. EnumerableEntityQuery(有限集合)
struct AllSoupsQuery: EnumerableEntityQuery {
func allEntities() async throws -> [SoupEntity] {
SoupStore.shared.allSoups.map { SoupEntity(from: $0) }
}
func entities(for identifiers: [String]) async throws -> [SoupEntity] {
SoupStore.shared.soups.filter { identifiers.contains($0.id) }.map { SoupEntity(from: $0) }
}
}
4. UniqueAppEntityQuery(单例,iOS 18+)
用于应用设置等单实例实体。
struct AppSettingsEntity: UniqueAppEntity {
static let defaultQuery = AppSettingsQuery()
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Settings"
var displayRepresentation: DisplayRepresentation { "App Settings" }
var id: String { "app-settings" }
}
struct AppSettingsQuery: UniqueAppEntityQuery {
func uniqueEntity() async throws -> AppSettingsEntity {
AppSettingsEntity()
}
}
有关支持筛选和排序的 EntityPropertyQuery,请参阅
references/appintents-advanced.md。
当一个参数决定另一个参数可选的实体时,请在查询或选项提供程序中使用
@IntentParameterDependency。高级参考文档介绍了上游值为 nil 的情况以及有意设置默认值的做法。
AppEnum
定义固定的可选值集合。RawValue 必须遵循
LosslessStringConvertible;优先使用 String 原始值,以便标识符易读且稳定。
enum SoupSize: String, AppEnum {
case small, medium, large
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Size"
static var caseDisplayRepresentations: [SoupSize: DisplayRepresentation] = [
.small: "Small",
.medium: "Medium",
.large: "Large"
]
}
// Valid, but less readable in saved shortcuts and URL representations
enum Priority: Int, AppEnum {
case low = 1, medium = 2, high = 3
}
// Preferred
enum Priority: String, AppEnum {
case low, medium, high
// ...
}
AppShortcutsProvider
注册预置快捷指令,这些快捷指令无需用户配置即可显示在 Siri 和“快捷指令”应用中。
struct MyAppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: OrderSoupIntent(),
phrases: [
"Order \(\.$soup) in \(.applicationName)",
"Get soup from \(.applicationName)"
],
shortTitle: "Order Soup",
systemImageName: "cup.and.saucer"
)
}
static var shortcutTileColor: ShortcutTileColor = .navy
}
短语规则
- 每个短语都必须包含
\(.applicationName)。 - 短语可以引用参数:
\(\.$soup)。 - 动态选项值发生变化时,调用
updateAppShortcutParameters()。 - 使用
negativePhrases防止 Siri 误触发。
系统界面集成
实现 Siri 捐赠/预测、交互式小组件、“控制中心”配置/操作分离,或 Spotlight IndexedEntity 索引时,请阅读 references/system-surfaces.md。
iOS 26 新增内容
SnippetIntent
在系统 UI 中显示交互式片段:
struct OrderStatusSnippet: SnippetIntent {
static var title: LocalizedStringResource = "Order Status"
func perform() async throws -> some IntentResult & ShowsSnippetView {
let status = await OrderTracker.currentStatus()
return .result(view: OrderStatusSnippetView(status: status))
}
}
struct CheckOrderStatusIntent: AppIntent {
static var title: LocalizedStringResource = "Check Order Status"
func perform() async throws -> some IntentResult & ShowsSnippetIntent {
.result(snippetIntent: OrderStatusSnippet())
}
}
系统可能会多次调用 perform(),包括在片段按钮或开关操作之后;请确保 SnippetIntent.perform() 不产生副作用,并在调用它的操作意图或单独的按钮/开关操作中执行变更。仅用于片段的意图不会显示在“快捷指令”或 Spotlight 中,除非 isDiscoverable 为 true。
IntentValueQuery(视觉智能)
@available(iOS 26, *)
@UnionValue
enum ShoppingVisualResult {
case product(ProductEntity)
case store(StoreEntity)
}
@available(iOS 26, *)
struct ShoppingVisualQuery: IntentValueQuery {
func values(for input: SemanticContentDescriptor) async throws -> [ShoppingVisualResult] {
try Task.checkCancellation()
async let productMatches = ProductStore.shared.matches(
labels: input.labels,
pixelBuffer: input.pixelBuffer,
limit: 5
)
async let storeMatches = StoreStore.shared.matches(
labels: input.labels,
pixelBuffer: input.pixelBuffer,
limit: 3
)
let ranked = await rank(productMatches, storeMatches)
return Array(ranked.prefix(8))
}
}
只有一个 IntentValueQuery 可以接收 SemanticContentDescriptor;当一个查询必须返回多种应用实体类型时,请使用 @UnionValue。将 labels 视为高层级的英文描述,而非穷举的同义词或应用分类体系;在可用时,将其与 pixelBuffer 结合使用。返回数量少、经过排序且支持取消的结果,并提供 OpenIntent、URL 表示形式或应用内搜索交接方式,以便查看详情和更多结果。不要在 App Intents 查询中实现相机拍摄、Vision VN* 请求、条码分类或 Spotlight 索引;应改为调用现有的、有范围限制的应用搜索或图像匹配服务,并在工作可能超出系统 UI 时限时明确设置结果上限和超时时间。
常见错误
-
通过 AppEntity 暴露过多应用模型状态。 优先使用专用的影子模型,采用稳定的持久 ID,并且只包含面向系统的属性。
-
短语中缺少
\(.applicationName)。 每个AppShortcut短语都必须包含应用名称标记。Siri 会使用它来消除歧义。 -
将可选的
@Parameter视为必填项。 系统不会自动请求可选参数;如果意图没有该参数就无法继续,请调用requestValue/needsValueError。// Optional, so request it yourself if needed @Parameter(title: "Count") var count: Int? -
使用不稳定的 AppEnum 原始值。
Int有效,但对于持久化和 URL 表示,String原始值通常更清晰。 -
忘记
suggestedEntities()。 没有它,快捷指令选取器不会显示默认项。 -
在
entities(for:)中对缺失的实体抛出错误。 应省略缺失的实体。 -
Spotlight 索引过期。 使用命名的
CSSearchableIndex重新索引实体。 -
缺少
typeDisplayRepresentation。AppEntity和AppEnum都需要它。 -
使用已弃用的
@Assistant*架构宏。 使用@AppIntent(schema:)、@AppEntity(schema:)和@AppEnum(schema:)。 -
阻塞或产生副作用的 perform()。 对 I/O 使用
await;让SnippetIntent.perform()保持无副作用,因为系统可能会重新运行它。 -
未设防就从系统界面修改敏感状态。 对门锁、灯、购买和删除等操作使用确认和/或身份验证。
审核清单
- 每个
AppIntent都有描述性title(动词 + 名词,标题式大小写) - 必填的
@Parameter值不可选;需要时再请求可选值 -
AppEntity类型公开稳定 ID,并且只包含面向系统的属性 -
AppEntity有displayRepresentation和typeDisplayRepresentation -
EntityQuery.entities(for:)省略缺失的 ID;已实现suggestedEntities() - 依赖选项使用
@IntentParameterDependency;仅当确实存在实用的默认值时才提供defaultResult() -
AppEnum优先使用稳定的String原始值,并提供caseDisplayRepresentations -
AppShortcutsProvider短语包含\(.applicationName);已定义parameterSummary -
IndexedEntity属性使用键路径indexingKey值,并且实体已建立索引 - 控制中心意图遵循
ControlConfigurationIntent;小组件意图遵循WidgetConfigurationIntent;没有默认值的控制参数必须可选 - 敏感的 App Intents 在修改状态前请求确认和/或身份验证
- Visual Intelligence
IntentValueQuery使用SemanticContentDescriptor、有限数量的结果、打开路径,并满足 iOS 26 可用性要求 - 没有已弃用的
@AssistantIntent/@AssistantEntity/@AssistantEnum架构宏 -
perform()使用 async/await(不阻塞);在预期的隔离上下文中运行;意图类型遵循Sendable
参考资料
- 阅读 references/system-surfaces.md,了解 Siri、小组件、控制中心和 Spotlight 集成模式。
- 参阅 references/appintents-advanced.md,了解
@Parameter变体、EntityPropertyQuery、助手架构、专注模式过滤器、SiriKit 迁移、错误处理、确认流程、身份验证、URL 可表示类型和 Spotlight 索引详情。