app-intents

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

App Intents (iOS 26+)

App Intents (iOS 26+)

Implement, review, and extend App Intents to expose app functionality to Siri, Shortcuts, Spotlight, widgets, Control Center, and Apple Intelligence.
实现、审查并扩展App Intents,将应用功能开放给Siri、Shortcuts、Spotlight、小组件、控制中心和Apple Intelligence调用。

Triage Workflow

处理工作流

Step 1: Identify the integration surface

步骤1:确定集成场景

Determine which system feature the intent targets:
SurfaceProtocolSince
Siri / Shortcuts
AppIntent
iOS 16
Configurable widget
WidgetConfigurationIntent
iOS 17
Control Center
ControlConfigurationIntent
iOS 18
Spotlight search
IndexedEntity
iOS 18
Apple Intelligence
@AppIntent(schema:)
iOS 18
Interactive snippets
SnippetIntent
iOS 26
Visual Intelligence
IntentValueQuery
iOS 26
确定该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

Step 2: Define the data model

步骤2:定义数据模型

  • Create
    AppEntity
    shadow models (do NOT conform core data models directly).
  • Create
    AppEnum
    types for fixed parameter choices.
  • Choose the right
    EntityQuery
    variant for resolution.
  • Mark searchable entities with
    IndexedEntity
    and
    @Property(indexingKey:)
    .
  • 创建
    AppEntity
    影子模型(不要直接让核心数据模型遵守该协议)。
  • 为固定参数选项创建
    AppEnum
    类型。
  • 选择合适的
    EntityQuery
    变体来做数据解析。
  • IndexedEntity
    @Property(indexingKey:)
    标记可搜索的实体。

Step 3: Implement the intent

步骤3:实现Intent

  • Conform to
    AppIntent
    (or a specialized sub-protocol).
  • Declare
    @Parameter
    properties for all user-facing inputs.
  • Implement
    perform() async throws -> some IntentResult
    .
  • Add
    parameterSummary
    for Shortcuts UI.
  • Register phrases via
    AppShortcutsProvider
    .
  • 遵守
    AppIntent
    协议(或对应的专用子协议)。
  • 为所有面向用户的输入声明
    @Parameter
    属性。
  • 实现
    perform() async throws -> some IntentResult
    方法。
  • 为Shortcuts UI添加
    parameterSummary
  • 通过
    AppShortcutsProvider
    注册触发短语。

Step 4: Verify

步骤4:验证

  • Build and run in Shortcuts app to confirm parameter resolution.
  • Test Siri phrases with the intent preview in Xcode.
  • Confirm Spotlight results for
    IndexedEntity
    types.
  • Check widget configuration for
    WidgetConfigurationIntent
    intents.
  • 在Shortcuts应用中构建并运行,确认参数解析正常。
  • 使用Xcode中的Intent预览测试Siri触发短语。
  • 确认
    IndexedEntity
    类型的Spotlight搜索结果正常。
  • 检查
    WidgetConfigurationIntent
    类型Intent的小组件配置功能正常。

AppIntent Protocol

AppIntent 协议

The system instantiates the struct via
init()
, sets parameters, then calls
perform()
. Declare a
title
and
parameterSummary
for Shortcuts UI.
swift
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).")
    }
}
Optional members:
description
(
IntentDescription
),
openAppWhenRun
(
Bool
),
isDiscoverable
(
Bool
),
authenticationPolicy
(
IntentAuthenticationPolicy
).
系统会通过
init()
实例化结构体,设置参数,之后调用
perform()
方法。需要声明
title
parameterSummary
供Shortcuts UI展示。
swift
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

Declare each user-facing input with
@Parameter
. Optional parameters are not required; non-optional parameters with a
default
are pre-filled.
swift
// WRONG: Non-optional parameter without default -- system cannot preview
@Parameter(title: "Count")
var count: Int

// CORRECT: Provide a default or make optional
@Parameter(title: "Count", default: 1)
var count: Int

@Parameter(title: "Count")
var count: Int?
@Parameter
声明每个面向用户的输入。可选参数不是必填项;带
default
默认值的非可选参数会被自动预填。
swift
// WRONG: Non-optional parameter without default -- system cannot preview
@Parameter(title: "Count")
var count: Int

// CORRECT: Provide a default or make optional
@Parameter(title: "Count", default: 1)
var count: Int

@Parameter(title: "Count")
var count: Int?

Supported value types

支持的值类型

Primitives:
Int
,
Double
,
Bool
,
String
,
URL
,
Date
,
DateComponents
. Framework:
Currency
,
Person
,
IntentFile
. Measurements:
Measurement<UnitLength>
,
Measurement<UnitTemperature>
, and others. Custom: any
AppEntity
or
AppEnum
.
基础类型:
Int
Double
Bool
String
URL
Date
DateComponents
。 框架类型:
Currency
Person
IntentFile
。计量单位:
Measurement<UnitLength>
Measurement<UnitTemperature>
等其他类型。自定义类型:任意遵守
AppEntity
AppEnum
协议的类型。

Common initializer patterns

常见初始化模式

swift
// 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>
See
references/appintents-advanced.md
for all initializer variants.
swift
// 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

AppEntity

Create shadow models that mirror app data -- never conform core data model types directly.
swift
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
    }
}
Required:
id
,
defaultQuery
(static),
displayRepresentation
,
typeDisplayRepresentation
(static). Mark properties with
@Property(title:)
to expose for filtering/sorting. Properties without
@Property
remain internal.
创建与应用数据映射的影子模型 -- 永远不要直接让核心数据模型类型遵守该协议。
swift
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 Variants)

EntityQuery (4种变体)

1. EntityQuery (base -- resolve by ID)

1. EntityQuery (基础版 -- 通过ID解析)

swift
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) }
    }
}
swift
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 (free-text search)

2. EntityStringQuery (自由文本搜索)

swift
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) }
    }
}
swift
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 (finite set)

3. EnumerableEntityQuery (有限集合)

swift
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) }
    }
}
swift
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 (singleton, iOS 18+)

4. UniqueAppEntityQuery (单例,iOS 18+)

Use for single-instance entities like app settings.
swift
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 entity() async throws -> AppSettingsEntity {
        AppSettingsEntity()
    }
}
See
references/appintents-advanced.md
for
EntityPropertyQuery
with filter/sort support.
用于应用设置等单实例实体。
swift
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 entity() async throws -> AppSettingsEntity {
        AppSettingsEntity()
    }
}
查看
references/appintents-advanced.md
了解支持过滤/排序的
EntityPropertyQuery

AppEnum

AppEnum

Define fixed sets of selectable values. Must be backed by a
LosslessStringConvertible
raw value (use
String
).
swift
enum SoupSize: String, AppEnum {
    case small, medium, large

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Size"

    static var caseDisplayRepresentations: [SoupSize: DisplayRepresentation] = [
        .small: "Small",
        .medium: "Medium",
        .large: "Large"
    ]
}
swift
// WRONG: Using Int raw value
enum Priority: Int, AppEnum { // Compiler error -- Int is not LosslessStringConvertible
    case low = 1, medium = 2, high = 3
}

// CORRECT: Use String raw value
enum Priority: String, AppEnum {
    case low, medium, high
    // ...
}
定义固定的可选值集合。必须使用遵循
LosslessStringConvertible
协议的原始值(推荐用
String
)。
swift
enum SoupSize: String, AppEnum {
    case small, medium, large

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Size"

    static var caseDisplayRepresentations: [SoupSize: DisplayRepresentation] = [
        .small: "Small",
        .medium: "Medium",
        .large: "Large"
    ]
}
swift
// WRONG: Using Int raw value
enum Priority: Int, AppEnum { // Compiler error -- Int is not LosslessStringConvertible
    case low = 1, medium = 2, high = 3
}

// CORRECT: Use String raw value
enum Priority: String, AppEnum {
    case low, medium, high
    // ...
}

AppShortcutsProvider

AppShortcutsProvider

Register pre-built shortcuts that appear in Siri and the Shortcuts app without user configuration.
swift
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
}
注册预构建的快捷指令,无需用户配置即可出现在Siri和Shortcuts应用中。
swift
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
}

Phrase rules

短语规则

  • Every phrase MUST include
    \(.applicationName)
    .
  • Phrases can reference parameters:
    \(\.$soup)
    .
  • Call
    updateAppShortcutParameters()
    when dynamic option values change.
  • Use
    negativePhrases
    to prevent false Siri activations.
  • 每个短语必须包含
    \(.applicationName)
  • 短语可以引用参数:
    \(\.$soup)
  • 动态选项值变更时调用
    updateAppShortcutParameters()
  • 使用
    negativePhrases
    避免Siri误触发。

Siri Integration

Siri 集成

Donating intents

捐赠Intent

Donate intents so the system learns user patterns and suggests them in Spotlight:
swift
let intent = OrderSoupIntent()
intent.soup = favoriteSoupEntity
try await intent.donate()
捐赠Intent以便系统学习用户使用习惯,并在Spotlight中给出相关建议:
swift
let intent = OrderSoupIntent()
intent.soup = favoriteSoupEntity
try await intent.donate()

Predictable intents

可预测Intent

Conform to
PredictableIntent
for Siri prediction of upcoming actions.
遵守
PredictableIntent
协议即可让Siri预测用户即将执行的操作。

Interactive Widget Intents

交互式小组件 Intent

Use
AppIntent
with
Button
/
Toggle
in widgets. Use
WidgetConfigurationIntent
for configurable widget parameters.
swift
struct ToggleFavoriteIntent: AppIntent {
    static var title: LocalizedStringResource = "Toggle Favorite"
    @Parameter(title: "Item ID") var itemID: String

    func perform() async throws -> some IntentResult {
        FavoriteStore.shared.toggle(itemID)
        return .result()
    }
}

// In widget view:
Button(intent: ToggleFavoriteIntent(itemID: entry.id)) {
    Image(systemName: entry.isFavorite ? "heart.fill" : "heart")
}
在小组件的
Button
/
Toggle
中使用
AppIntent
。使用
WidgetConfigurationIntent
定义可配置的小组件参数。
swift
struct ToggleFavoriteIntent: AppIntent {
    static var title: LocalizedStringResource = "Toggle Favorite"
    @Parameter(title: "Item ID") var itemID: String

    func perform() async throws -> some IntentResult {
        FavoriteStore.shared.toggle(itemID)
        return .result()
    }
}

// 在小组件视图中:
Button(intent: ToggleFavoriteIntent(itemID: entry.id)) {
    Image(systemName: entry.isFavorite ? "heart.fill" : "heart")
}

WidgetConfigurationIntent

WidgetConfigurationIntent

swift
struct BookWidgetConfig: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "Favorite Book"
    @Parameter(title: "Book", default: "The Swift Programming Language") var bookTitle: String
}

// Connect to WidgetKit:
struct MyWidget: Widget {
    var body: some WidgetConfiguration {
        AppIntentConfiguration(kind: "FavoriteBook", intent: BookWidgetConfig.self, provider: MyTimelineProvider()) { entry in
            BookWidgetView(entry: entry)
        }
    }
}
swift
struct BookWidgetConfig: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "Favorite Book"
    @Parameter(title: "Book", default: "The Swift Programming Language") var bookTitle: String
}

// 连接到WidgetKit:
struct MyWidget: Widget {
    var body: some WidgetConfiguration {
        AppIntentConfiguration(kind: "FavoriteBook", intent: BookWidgetConfig.self, provider: MyTimelineProvider()) { entry in
            BookWidgetView(entry: entry)
        }
    }
}

Control Center Widgets (iOS 18+)

控制中心小组件 (iOS 18+)

Expose controls in Control Center and Lock Screen with
ControlConfigurationIntent
and
ControlWidget
.
swift
struct LightControlConfig: ControlConfigurationIntent {
    static var title: LocalizedStringResource = "Light Control"
    @Parameter(title: "Light", default: .livingRoom) var light: LightEntity
}

struct ToggleLightIntent: AppIntent {
    static var title: LocalizedStringResource = "Toggle Light"
    @Parameter(title: "Light") var light: LightEntity
    func perform() async throws -> some IntentResult {
        try await LightService.shared.toggle(light.id)
        return .result()
    }
}

struct LightControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        AppIntentControlConfiguration(kind: "LightControl", intent: LightControlConfig.self) { config in
            ControlWidgetToggle(config.light.name, isOn: config.light.isOn, action: ToggleLightIntent(light: config.light))
        }
    }
}
通过
ControlConfigurationIntent
ControlWidget
将控制功能开放到控制中心和锁屏。
swift
struct LightControlConfig: ControlConfigurationIntent {
    static var title: LocalizedStringResource = "Light Control"
    @Parameter(title: "Light", default: .livingRoom) var light: LightEntity
}

struct ToggleLightIntent: AppIntent {
    static var title: LocalizedStringResource = "Toggle Light"
    @Parameter(title: "Light") var light: LightEntity
    func perform() async throws -> some IntentResult {
        try await LightService.shared.toggle(light.id)
        return .result()
    }
}

struct LightControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        AppIntentControlConfiguration(kind: "LightControl", intent: LightControlConfig.self) { config in
            ControlWidgetToggle(config.light.name, isOn: config.light.isOn, action: ToggleLightIntent(light: config.light))
        }
    }
}

Spotlight and IndexedEntity (iOS 18+)

Spotlight 和 IndexedEntity (iOS 18+)

Conform to
IndexedEntity
for Spotlight search. On iOS 26+, use
indexingKey
for structured metadata:
swift
struct RecipeEntity: IndexedEntity {
    static let defaultQuery = RecipeQuery()
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Recipe"
    var id: String

    @Property(title: "Name", indexingKey: .title) var name: String   // iOS 26+
    @ComputedProperty(indexingKey: .description)                      // iOS 26+
    var summary: String { "\(name) -- a delicious recipe" }

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(name)")
    }
}
遵守
IndexedEntity
协议即可支持Spotlight搜索。在iOS 26+中,使用
indexingKey
定义结构化元数据:
swift
struct RecipeEntity: IndexedEntity {
    static let defaultQuery = RecipeQuery()
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Recipe"
    var id: String

    @Property(title: "Name", indexingKey: .title) var name: String   // iOS 26+
    @ComputedProperty(indexingKey: .description)                      // iOS 26+
    var summary: String { "\(name) -- a delicious recipe" }

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(name)")
    }
}

iOS 26 Additions

iOS 26 新增功能

SnippetIntent

SnippetIntent

Display interactive snippets in system UI:
swift
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))
    }
    static func reload() { /* notify system to refresh */ }
}

// A calling intent can display this snippet via:
// return .result(snippetIntent: OrderStatusSnippet())
在系统UI中展示交互式片段:
swift
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))
    }
    static func reload() { /* notify system to refresh */ }
}

// 调用方Intent可以通过以下代码展示该片段:
// return .result(snippetIntent: OrderStatusSnippet())

IntentValueQuery (Visual Intelligence)

IntentValueQuery (视觉智能)

swift
struct ProductValueQuery: IntentValueQuery {
    typealias Input = String
    typealias Result = ProductEntity
    func values(for input: String) async throws -> [ProductEntity] {
        ProductStore.shared.search(input).map { ProductEntity(from: $0) }
    }
}
swift
struct ProductValueQuery: IntentValueQuery {
    typealias Input = String
    typealias Result = ProductEntity
    func values(for input: String) async throws -> [ProductEntity] {
        ProductStore.shared.search(input).map { ProductEntity(from: $0) }
    }
}

Common Mistakes

常见错误

  1. Conforming core data models to AppEntity. Create dedicated shadow models instead. Core models carry persistence logic that conflicts with intent lifecycle.
  2. Missing
    \(.applicationName)
    in phrases.
    Every
    AppShortcut
    phrase MUST include the application name token. Siri uses it for disambiguation.
  3. Non-optional @Parameter without default. The system cannot preview or pre-fill such parameters. Make non-optional parameters have a
    default
    , or mark them optional.
    swift
    // WRONG
    @Parameter(title: "Count")
    var count: Int
    
    // CORRECT
    @Parameter(title: "Count", default: 1)
    var count: Int
  4. Using Int raw value for AppEnum.
    AppEnum
    requires
    RawRepresentable
    where
    RawValue: LosslessStringConvertible
    . Use
    String
    .
  5. Forgetting
    suggestedEntities()
    .
    Without it, the Shortcuts picker shows no default options. Implement it on every
    EntityQuery
    .
  6. Throwing for missing entities in
    entities(for:)
    .
    Omit missing entities from the returned array instead of throwing an error.
  7. Stale Spotlight index. Call
    updateAppShortcutParameters()
    when entity data changes. For
    IndexedEntity
    , re-donate or update the entity.
  8. Missing
    typeDisplayRepresentation
    on AppEntity or AppEnum.
    Both protocols require a static
    typeDisplayRepresentation
    . Omitting it causes a compiler error that can be confusing.
  9. Using deprecated
    @AssistantIntent(schema:)
    .
    Use
    @AppIntent(schema:)
    instead. The
    @AssistantIntent
    macro was deprecated in iOS 18.4.
  10. Blocking perform() with synchronous work.
    perform()
    is async -- use
    await
    for I/O. Never block the thread with synchronous network calls.
  1. 让核心数据模型直接遵守AppEntity协议。 请改为创建专用的影子模型。核心数据模型携带的持久化逻辑会与Intent生命周期冲突。
  2. 短语中缺少
    \(.applicationName)
    每个
    AppShortcut
    短语都必须包含应用名称标记,Siri会用它来做指令消歧。
  3. 非可选@Parameter没有默认值。 系统无法为这类参数生成预览或预填内容。请为非可选参数设置
    default
    默认值,或将其标记为可选。
    swift
    // 错误写法
    @Parameter(title: "Count")
    var count: Int
    
    // 正确写法
    @Parameter(title: "Count", default: 1)
    var count: Int
  4. AppEnum使用Int类型作为原始值。
    AppEnum
    要求
    RawRepresentable
    RawValue
    遵循
    LosslessStringConvertible
    协议,请使用
    String
    类型。
  5. 忘记实现
    suggestedEntities()
    没有该方法的话,Shortcuts选择器不会展示任何默认选项。请为每个
    EntityQuery
    实现该方法。
  6. entities(for:)
    中为缺失的实体抛出错误。
    请直接从返回数组中省略缺失的实体,不要抛出错误。
  7. Spotlight索引过期。 实体数据变更时请调用
    updateAppShortcutParameters()
    。对于
    IndexedEntity
    ,请重新捐赠或更新实体。
  8. AppEntity或AppEnum缺少
    typeDisplayRepresentation
    这两个协议都要求实现静态的
    typeDisplayRepresentation
    属性,省略会导致难以排查的编译错误。
  9. 使用已废弃的
    @AssistantIntent(schema:)
    请改用
    @AppIntent(schema:)
    @AssistantIntent
    宏在iOS 18.4中已被废弃。
  10. perform()
    中执行同步阻塞任务。
    perform()
    是异步方法,I/O操作请使用
    await
    ,永远不要用同步网络请求阻塞线程。

Review Checklist

审查清单

  • Every
    AppIntent
    has a descriptive
    title
    (verb + noun, title case)
  • @Parameter
    types are optional or have defaults for system preview
  • AppEntity
    types are shadow models, not core data model conformances
  • AppEntity
    has
    displayRepresentation
    and
    typeDisplayRepresentation
  • EntityQuery.entities(for:)
    omits missing IDs (does not throw)
  • suggestedEntities()
    implemented on all entity queries
  • AppEnum
    uses
    String
    raw value with
    caseDisplayRepresentations
  • AppShortcutsProvider
    phrases include
    \(.applicationName)
  • parameterSummary
    defined for Shortcuts UI readability
  • IndexedEntity
    properties use
    @Property(indexingKey:)
    on iOS 26+
  • Control Center intents conform to
    ControlConfigurationIntent
  • Widget intents conform to
    WidgetConfigurationIntent
  • No deprecated
    @AssistantIntent
    /
    @AssistantEntity
    macros
  • perform()
    uses async/await, no synchronous blocking
  • perform()
    runs in expected isolation context; intent parameter types are
    Sendable
  • 每个
    AppIntent
    都有描述清晰的
    title
    (动词+名词,标题大小写)
  • @Parameter
    类型要么是可选,要么带有默认值支持系统预览
  • AppEntity
    类型是影子模型,不是直接遵守协议的核心数据模型
  • AppEntity
    实现了
    displayRepresentation
    typeDisplayRepresentation
  • EntityQuery.entities(for:)
    会省略缺失的ID(不会抛出错误)
  • 所有实体查询都实现了
    suggestedEntities()
  • AppEnum
    使用
    String
    作为原始值并实现了
    caseDisplayRepresentations
  • AppShortcutsProvider
    的短语包含
    \(.applicationName)
  • 实现了
    parameterSummary
    保证Shortcuts UI的可读性
  • iOS 26+上的
    IndexedEntity
    属性使用了
    @Property(indexingKey:)
  • 控制中心Intent遵守
    ControlConfigurationIntent
    协议
  • 小组件Intent遵守
    WidgetConfigurationIntent
    协议
  • 没有使用已废弃的
    @AssistantIntent
    /
    @AssistantEntity
  • perform()
    使用async/await,没有同步阻塞逻辑
  • perform()
    运行在预期的隔离上下文中;Intent参数类型是
    Sendable

Reference Material

参考材料

  • See
    references/appintents-advanced.md
    for @Parameter variants, EntityPropertyQuery, assistant schemas, focus filters, SiriKit migration, error handling, confirmation flows, authentication, URL-representable types, and Spotlight indexing details.
  • 查看
    references/appintents-advanced.md
    了解@Parameter变体、EntityPropertyQuery、助手schema、专注过滤器、SiriKit迁移、错误处理、确认流程、鉴权、URL可表示类型以及Spotlight索引的详细信息。