mcpField
A
app-intents

app-intents

github.com/dpearson2699开发1.2k4.0k 次安装13 周前更新
代码仓库

关于 app-intents

用于实现和审查 iOS App Intents 的开发技能,帮助将应用操作和实体提供给 Siri、Shortcuts、Spotlight、小组件、控制中心及 Apple Intelligence,并涵盖 iOS 26 新增 API。

主要功能

  1. 01AppIntent 与参数设计
  2. 02AppEntity 与实体查询
  3. 03AppEnum 与快捷指令短语
  4. 04系统界面集成指引
  5. 05iOS 26 交互片段与视觉查询
  6. 06实现审查清单

适用场景

  1. 01让 Siri 和 Shortcuts 调用应用操作
  2. 02为 Spotlight 搜索提供应用实体
  3. 03配置小组件或控制中心操作

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill app-intents

安装后在 Claude Code 中输入/app-intents确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
appintents-advanced.md
system-surfaces.md

详细介绍

App Intents(iOS 26+)

实现、审查和扩展 App Intents,将应用功能开放给 Siri、 Shortcuts、Spotlight、小组件、控制中心和 Apple Intelligence。

目录

分类处理工作流

第 1 步:选择操作、边界和界面

从用户希望在应用之外使用的 1-3 个有价值的操作入手,而不是从应用的屏幕层级入手。每个操作记录一行设计信息:用户目标;内联结果或应用目的地;共享的领域操作;参数和实体查询;确认/身份验证;目标界面和协议。使用一个明确的运行时路由来处理应用跳转,而不要在各个 intent 中散布导航副作用。

然后,为该操作选择合适的系统功能和协议:

界面协议起始版本
Siri / ShortcutsAppIntentiOS 16
可配置小组件WidgetConfigurationIntentiOS 17
控制中心ControlConfigurationIntentiOS 18
Spotlight 搜索IndexedEntityiOS 18
Apple Intelligence@AppIntent(schema:)iOS 18
交互式片段SnippetIntentiOS 26
视觉智能IntentValueQueryiOS 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 时限时明确设置结果上限和超时时间。

常见错误

  1. 通过 AppEntity 暴露过多应用模型状态。 优先使用专用的影子模型,采用稳定的持久 ID,并且只包含面向系统的属性。

  2. 短语中缺少 \(.applicationName)。 每个 AppShortcut 短语都必须包含应用名称标记。Siri 会使用它来消除歧义。

  3. 将可选的 @Parameter 视为必填项。 系统不会自动请求可选参数;如果意图没有该参数就无法继续,请调用 requestValue / needsValueError。

    // Optional, so request it yourself if needed
    @Parameter(title: "Count")
    var count: Int?
    
  4. 使用不稳定的 AppEnum 原始值。 Int 有效,但对于持久化和 URL 表示,String 原始值通常更清晰。

  5. 忘记 suggestedEntities()。 没有它,快捷指令选取器不会显示默认项。

  6. 在 entities(for:) 中对缺失的实体抛出错误。 应省略缺失的实体。

  7. Spotlight 索引过期。 使用命名的 CSSearchableIndex 重新索引实体。

  8. 缺少 typeDisplayRepresentation。 AppEntity 和 AppEnum 都需要它。

  9. 使用已弃用的 @Assistant* 架构宏。 使用 @AppIntent(schema:)、@AppEntity(schema:) 和 @AppEnum(schema:)。

  10. 阻塞或产生副作用的 perform()。 对 I/O 使用 await;让 SnippetIntent.perform() 保持无副作用,因为系统可能会重新运行它。

  11. 未设防就从系统界面修改敏感状态。 对门锁、灯、购买和删除等操作使用确认和/或身份验证。

审核清单

  • 每个 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 索引详情。

评论

支持 Markdown