axiom-app-intents-ref
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseApp Intents Integration
App Intents 集成
Overview
概述
Comprehensive guide to App Intents framework for exposing app functionality to Siri, Apple Intelligence, Shortcuts, Spotlight, and other system experiences. Replaces older SiriKit custom intents with modern Swift-first API.
Core principle App Intents make your app's actions discoverable across Apple's ecosystem. Well-designed intents feel natural in Siri conversations, Shortcuts automation, and Spotlight search.
为将应用功能暴露给Siri、Apple Intelligence、快捷指令、聚焦搜索及其他系统体验提供的App Intents框架综合指南。以现代Swift优先API替代旧版SiriKit自定义意图。
核心原则 App Intents让您的应用操作在Apple生态系统中可被发现。设计良好的意图在Siri对话、快捷指令自动化和聚焦搜索中会显得自然流畅。
When to Use This Skill
何时使用此技能
- Exposing app functionality to Siri and Apple Intelligence
- Making app actions available in Shortcuts app
- Enabling Spotlight search for app content
- Integrating with Focus filters, widgets, Live Activities
- Adding Action button support (Apple Watch Ultra)
- Debugging intent resolution or parameter validation failures
- Testing intents with Shortcuts app
- Implementing entity queries for app content
- 向Siri和Apple Intelligence暴露应用功能
- 在快捷指令应用中提供应用操作
- 为应用内容启用聚焦搜索
- 与专注模式过滤器、小组件、实时活动集成
- 添加操作按钮支持(Apple Watch Ultra)
- 调试意图解析或参数验证失败问题
- 在快捷指令应用中测试意图
- 为应用内容实现实体查询
Related Skills
相关技能
- app-shortcuts-ref — App Shortcuts for instant Siri/Spotlight availability without user setup
- core-spotlight-ref — Core Spotlight and NSUserActivity integration for content indexing
- app-discoverability — Strategic guide for making apps surface system-wide across all APIs
- app-shortcuts-ref — App Shortcuts,无需用户设置即可立即在Siri/聚焦搜索中使用
- core-spotlight-ref — Core Spotlight与NSUserActivity集成,用于内容索引
- app-discoverability — 跨所有API让应用在系统范围内被发现的策略指南
System Experiences Supported
支持的系统体验
App Intents integrate with:
- Siri — Voice commands and Apple Intelligence
- Shortcuts — Automation workflows
- App Shortcuts — Pre-configured actions available instantly (see app-shortcuts-ref)
- Spotlight — Search discovery
- Focus Filters — Contextual filtering
- Action Button — Quick actions (Apple Watch Ultra)
- Control Center — Custom controls
- WidgetKit — Interactive widgets
- Live Activities — Dynamic Island updates
- Visual Intelligence — Image-based interactions
App Intents可与以下功能集成:
- Siri — 语音命令和Apple Intelligence
- 快捷指令 — 自动化工作流
- App Shortcuts — 预配置的即时可用操作(参见app-shortcuts-ref)
- 聚焦搜索 — 搜索发现
- 专注模式过滤器 — 上下文过滤
- 操作按钮 — 快速操作(Apple Watch Ultra)
- 控制中心 — 自定义控件
- WidgetKit — 交互式小组件
- 实时活动 — 灵动岛更新
- 视觉智能 — 基于图像的交互
Core Concepts
核心概念
The Three Building Blocks
三大构建模块
1. AppIntent — Executable actions with parameters
swift
struct OrderSoupIntent: AppIntent {
static var title: LocalizedStringResource = "Order Soup"
static var description: IntentDescription = "Orders soup from the restaurant"
@Parameter(title: "Soup")
var soup: SoupEntity
@Parameter(title: "Quantity")
var quantity: Int?
func perform() async throws -> some IntentResult {
guard let quantity = quantity, quantity < 10 else {
throw $quantity.needsValue("Please specify how many soups")
}
try await OrderService.shared.order(soup: soup, quantity: quantity)
return .result()
}
}2. AppEntity — Objects users interact with
swift
struct SoupEntity: AppEntity {
var id: String
var name: String
var price: Decimal
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Soup"
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)", subtitle: "$\(price)")
}
static var defaultQuery = SoupQuery()
}3. AppEnum — Enumeration types for parameters
swift
enum SoupSize: String, AppEnum {
case small
case medium
case large
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Size"
static var caseDisplayRepresentations: [SoupSize: DisplayRepresentation] = [
.small: "Small (8 oz)",
.medium: "Medium (12 oz)",
.large: "Large (16 oz)"
]
}1. AppIntent — 带参数的可执行操作
swift
struct OrderSoupIntent: AppIntent {
static var title: LocalizedStringResource = "Order Soup"
static var description: IntentDescription = "Orders soup from the restaurant"
@Parameter(title: "Soup")
var soup: SoupEntity
@Parameter(title: "Quantity")
var quantity: Int?
func perform() async throws -> some IntentResult {
guard let quantity = quantity, quantity < 10 else {
throw $quantity.needsValue("Please specify how many soups")
}
try await OrderService.shared.order(soup: soup, quantity: quantity)
return .result()
}
}2. AppEntity — 用户交互的对象
swift
struct SoupEntity: AppEntity {
var id: String
var name: String
var price: Decimal
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Soup"
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)", subtitle: "$\(price)")
}
static var defaultQuery = SoupQuery()
}3. AppEnum — 用于参数的枚举类型
swift
enum SoupSize: String, AppEnum {
case small
case medium
case large
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Size"
static var caseDisplayRepresentations: [SoupSize: DisplayRepresentation] = [
.small: "Small (8 oz)",
.medium: "Medium (12 oz)",
.large: "Large (16 oz)"
]
}AppIntent: Defining Actions
AppIntent:定义操作
Essential Properties
必备属性
swift
struct SendMessageIntent: AppIntent {
// REQUIRED: Short verb-noun phrase
static var title: LocalizedStringResource = "Send Message"
// REQUIRED: Purpose explanation
static var description: IntentDescription = "Sends a message to a contact"
// OPTIONAL: Discovery in Shortcuts/Spotlight
static var isDiscoverable: Bool = true
// OPTIONAL: Launch app when run
static var openAppWhenRun: Bool = false
// OPTIONAL: Authentication requirement
static var authenticationPolicy: IntentAuthenticationPolicy = .requiresAuthentication
}swift
struct SendMessageIntent: AppIntent {
// REQUIRED: Short verb-noun phrase
static var title: LocalizedStringResource = "Send Message"
// REQUIRED: Purpose explanation
static var description: IntentDescription = "Sends a message to a contact"
// OPTIONAL: Discovery in Shortcuts/Spotlight
static var isDiscoverable: Bool = true
// OPTIONAL: Launch app when run
static var openAppWhenRun: Bool = false
// OPTIONAL: Authentication requirement
static var authenticationPolicy: IntentAuthenticationPolicy = .requiresAuthentication
}Parameter Declaration
参数声明
swift
struct BookAppointmentIntent: AppIntent {
// Required parameter (non-optional)
@Parameter(title: "Service")
var service: ServiceEntity
// Optional parameter
@Parameter(title: "Preferred Date")
var preferredDate: Date?
// Parameter with requestValueDialog for disambiguation
@Parameter(title: "Location",
requestValueDialog: "Which location would you like to visit?")
var location: LocationEntity
// Parameter with default value
@Parameter(title: "Duration")
var duration: Int = 60
}swift
struct BookAppointmentIntent: AppIntent {
// Required parameter (non-optional)
@Parameter(title: "Service")
var service: ServiceEntity
// Optional parameter
@Parameter(title: "Preferred Date")
var preferredDate: Date?
// Parameter with requestValueDialog for disambiguation
@Parameter(title: "Location",
requestValueDialog: "Which location would you like to visit?")
var location: LocationEntity
// Parameter with default value
@Parameter(title: "Duration")
var duration: Int = 60
}Parameter Summary (Siri Phrasing)
参数摘要(Siri表述)
swift
struct OrderIntent: AppIntent {
@Parameter(title: "Item")
var item: MenuItem
@Parameter(title: "Quantity")
var quantity: Int
static var parameterSummary: some ParameterSummary {
Summary("Order \(\.$quantity) \(\.$item)") {
\.$quantity
\.$item
}
}
}
// Siri: "Order 2 lattes"swift
struct OrderIntent: AppIntent {
@Parameter(title: "Item")
var item: MenuItem
@Parameter(title: "Quantity")
var quantity: Int
static var parameterSummary: some ParameterSummary {
Summary("Order \(\.$quantity) \(\.$item)") {
\.$quantity
\.$item
}
}
}
// Siri: "Order 2 lattes"The perform() Method
perform() 方法
swift
func perform() async throws -> some IntentResult {
// 1. Validate parameters
guard quantity > 0 && quantity < 100 else {
throw ValidationError.invalidQuantity
}
// 2. Execute action
let order = try await orderService.placeOrder(
item: item,
quantity: quantity
)
// 3. Donate for learning (optional)
await donation()
// 4. Return result
return .result(
value: order,
dialog: "Your order for \(quantity) \(item.name) has been placed"
)
}swift
func perform() async throws -> some IntentResult {
// 1. Validate parameters
guard quantity > 0 && quantity < 100 else {
throw ValidationError.invalidQuantity
}
// 2. Execute action
let order = try await orderService.placeOrder(
item: item,
quantity: quantity
)
// 3. Donate for learning (optional)
await donation()
// 4. Return result
return .result(
value: order,
dialog: "Your order for \(quantity) \(item.name) has been placed"
)
}Error Handling
错误处理
swift
enum OrderError: Error, CustomLocalizedStringResourceConvertible {
case outOfStock(itemName: String)
case paymentFailed
case networkError
var localizedStringResource: LocalizedStringResource {
switch self {
case .outOfStock(let name):
return "Sorry, \(name) is out of stock"
case .paymentFailed:
return "Payment failed. Please check your payment method"
case .networkError:
return "Network error. Please try again"
}
}
}
func perform() async throws -> some IntentResult {
if !item.isInStock {
throw OrderError.outOfStock(itemName: item.name)
}
// ...
}swift
enum OrderError: Error, CustomLocalizedStringResourceConvertible {
case outOfStock(itemName: String)
case paymentFailed
case networkError
var localizedStringResource: LocalizedStringResource {
switch self {
case .outOfStock(let name):
return "Sorry, \(name) is out of stock"
case .paymentFailed:
return "Payment failed. Please check your payment method"
case .networkError:
return "Network error. Please try again"
}
}
}
func perform() async throws -> some IntentResult {
if !item.isInStock {
throw OrderError.outOfStock(itemName: item.name)
}
// ...
}AppEntity: Representing App Content
AppEntity:表示应用内容
Entity Definition
实体定义
swift
struct BookEntity: AppEntity {
// REQUIRED: Unique, persistent identifier
var id: UUID
// App data properties
var title: String
var author: String
var coverImageURL: URL?
// REQUIRED: Type display name
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Book"
// REQUIRED: Instance display
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "by \(author)",
image: coverImageURL.map { .init(url: $0) }
)
}
// REQUIRED: Query for resolution
static var defaultQuery = BookQuery()
}swift
struct BookEntity: AppEntity {
// REQUIRED: Unique, persistent identifier
var id: UUID
// App data properties
var title: String
var author: String
var coverImageURL: URL?
// REQUIRED: Type display name
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Book"
// REQUIRED: Instance display
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "by \(author)",
image: coverImageURL.map { .init(url: $0) }
)
}
// REQUIRED: Query for resolution
static var defaultQuery = BookQuery()
}Exposing Properties
暴露属性
swift
struct TaskEntity: AppEntity {
var id: UUID
@Property(title: "Title")
var title: String
@Property(title: "Due Date")
var dueDate: Date?
@Property(title: "Priority")
var priority: TaskPriority
@Property(title: "Completed")
var isCompleted: Bool
// Properties exposed to system for filtering/sorting
}swift
struct TaskEntity: AppEntity {
var id: UUID
@Property(title: "Title")
var title: String
@Property(title: "Due Date")
var dueDate: Date?
@Property(title: "Priority")
var priority: TaskPriority
@Property(title: "Completed")
var isCompleted: Bool
// Properties exposed to system for filtering/sorting
}Entity Query
实体查询
swift
struct BookQuery: EntityQuery {
func entities(for identifiers: [UUID]) async throws -> [BookEntity] {
// Fetch entities by IDs
return try await BookService.shared.fetchBooks(ids: identifiers)
}
func suggestedEntities() async throws -> [BookEntity] {
// Provide suggestions (recent, favorites, etc.)
return try await BookService.shared.recentBooks(limit: 10)
}
}
// Optional: Enable string-based search
extension BookQuery: EntityStringQuery {
func entities(matching string: String) async throws -> [BookEntity] {
return try await BookService.shared.searchBooks(query: string)
}
}swift
struct BookQuery: EntityQuery {
func entities(for identifiers: [UUID]) async throws -> [BookEntity] {
// Fetch entities by IDs
return try await BookService.shared.fetchBooks(ids: identifiers)
}
func suggestedEntities() async throws -> [BookEntity] {
// Provide suggestions (recent, favorites, etc.)
return try await BookService.shared.recentBooks(limit: 10)
}
}
// Optional: Enable string-based search
extension BookQuery: EntityStringQuery {
func entities(matching string: String) async throws -> [BookEntity] {
return try await BookService.shared.searchBooks(query: string)
}
}Separating Entities from Models
将实体与模型分离
❌ DON'T: Modify core data models
❌ 不要:修改核心数据模型
swift
// DON'T make your model conform to AppEntity
struct Book: AppEntity { // Bad - couples model to intents
var id: UUID
var title: String
// ...
}swift
// DON'T make your model conform to AppEntity
struct Book: AppEntity { // Bad - couples model to intents
var id: UUID
var title: String
// ...
}✅ DO: Create dedicated entities
✅ 要:创建专用实体
swift
// Your core model
struct Book {
var id: UUID
var title: String
var isbn: String
var pages: Int
// ... lots of internal properties
}
// Separate entity for intents
struct BookEntity: AppEntity {
var id: UUID
var title: String
var author: String
// Convert from model
init(from book: Book) {
self.id = book.id
self.title = book.title
self.author = book.author.name
}
}swift
// Your core model
struct Book {
var id: UUID
var title: String
var isbn: String
var pages: Int
// ... lots of internal properties
}
// Separate entity for intents
struct BookEntity: AppEntity {
var id: UUID
var title: String
var author: String
// Convert from model
init(from book: Book) {
self.id = book.id
self.title = book.title
self.author = book.author.name
}
}Authentication & Security
身份验证与安全
Authentication Policies
身份验证策略
swift
struct ViewAccountIntent: AppIntent {
// No authentication required
static var authenticationPolicy: IntentAuthenticationPolicy = .alwaysAllowed
}
struct TransferMoneyIntent: AppIntent {
// Requires user to be logged in
static var authenticationPolicy: IntentAuthenticationPolicy = .requiresAuthentication
}
struct UnlockVaultIntent: AppIntent {
// Requires device unlock (Face ID/Touch ID/passcode)
static var authenticationPolicy: IntentAuthenticationPolicy = .requiresLocalDeviceAuthentication
}swift
struct ViewAccountIntent: AppIntent {
// No authentication required
static var authenticationPolicy: IntentAuthenticationPolicy = .alwaysAllowed
}
struct TransferMoneyIntent: AppIntent {
// Requires user to be logged in
static var authenticationPolicy: IntentAuthenticationPolicy = .requiresAuthentication
}
struct UnlockVaultIntent: AppIntent {
// Requires device unlock (Face ID/Touch ID/passcode)
static var authenticationPolicy: IntentAuthenticationPolicy = .requiresLocalDeviceAuthentication
}Background vs Foreground Execution
后台与前台执行
Background Execution
后台执行
swift
struct QuickToggleIntent: AppIntent {
static var openAppWhenRun: Bool = false // Runs in background
func perform() async throws -> some IntentResult {
// Executes without opening app
await SettingsService.shared.toggle(setting: .darkMode)
return .result()
}
}swift
struct QuickToggleIntent: AppIntent {
static var openAppWhenRun: Bool = false // Runs in background
func perform() async throws -> some IntentResult {
// Executes without opening app
await SettingsService.shared.toggle(setting: .darkMode)
return .result()
}
}Foreground Continuation
前台续接
swift
struct EditDocumentIntent: AppIntent {
@Parameter(title: "Document")
var document: DocumentEntity
func perform() async throws -> some IntentResult {
// Open app to continue in UI
return .result(opensIntent: OpenDocumentIntent(document: document))
}
}
struct OpenDocumentIntent: AppIntent {
static var openAppWhenRun: Bool = true
@Parameter(title: "Document")
var document: DocumentEntity
func perform() async throws -> some IntentResult {
// App is now foreground, safe to update UI
await MainActor.run {
DocumentCoordinator.shared.open(document: document)
}
return .result()
}
}swift
struct EditDocumentIntent: AppIntent {
@Parameter(title: "Document")
var document: DocumentEntity
func perform() async throws -> some IntentResult {
// Open app to continue in UI
return .result(opensIntent: OpenDocumentIntent(document: document))
}
}
struct OpenDocumentIntent: AppIntent {
static var openAppWhenRun: Bool = true
@Parameter(title: "Document")
var document: DocumentEntity
func perform() async throws -> some IntentResult {
// App is now foreground, safe to update UI
await MainActor.run {
DocumentCoordinator.shared.open(document: document)
}
return .result()
}
}Confirmation Dialogs
确认对话框
Requesting Confirmation
请求确认
swift
struct DeleteTaskIntent: AppIntent {
@Parameter(title: "Task")
var task: TaskEntity
func perform() async throws -> some IntentResult {
// Request confirmation before destructive action
try await requestConfirmation(
result: .result(dialog: "Are you sure you want to delete '\(task.title)'?"),
confirmationActionName: .init(stringLiteral: "Delete")
)
// User confirmed, proceed
try await TaskService.shared.delete(task: task)
return .result(dialog: "Task deleted")
}
}swift
struct DeleteTaskIntent: AppIntent {
@Parameter(title: "Task")
var task: TaskEntity
func perform() async throws -> some IntentResult {
// Request confirmation before destructive action
try await requestConfirmation(
result: .result(dialog: "Are you sure you want to delete '\(task.title)'?"),
confirmationActionName: .init(stringLiteral: "Delete")
)
// User confirmed, proceed
try await TaskService.shared.delete(task: task)
return .result(dialog: "Task deleted")
}
}Apple Intelligence: Use Model Action
Apple Intelligence:使用模型操作
Overview
概述
The Use Model action in Shortcuts (iOS 18.1+) allows users to incorporate Apple Intelligence models into their automation workflows. Your app's entities can be passed to language models for filtering, transformation, and reasoning.
Key capability Under the hood, the action passes a JSON representation of your entity to the model, so you'll want to make sure to expose any information you want it to be able to reason over, in the entity definition.
快捷指令中的使用模型操作(iOS 18.1+)允许用户将Apple Intelligence模型纳入其自动化工作流。您应用的实体可传递给语言模型进行过滤、转换和推理。
核心能力 在底层,该操作会将您实体的JSON表示传递给模型,因此您需要确保在实体定义中暴露所有希望模型能够推理的信息。
Three Output Types
三种输出类型
1. Text (AttributedString)
1. 文本(AttributedString)
- Models often respond with Rich Text (bold, italic, lists, tables)
- Use type for text parameters to preserve formatting
AttributedString - Enables lossless transfer from model to your app
- 模型通常会返回富文本(粗体、斜体、列表、表格)
- 对文本参数使用类型以保留格式
AttributedString - 实现从模型到应用的无损传输
2. Dictionary
2. 字典
- Structured data extraction from unstructured input
- Useful for parsing PDFs, emails, documents
- Example: Extract vendor, amount, date from invoice
- 从非结构化输入中提取结构化数据
- 适用于解析PDF、电子邮件、文档
- 示例:从发票中提取供应商、金额、日期
3. App Entities (Your Types)
3. App实体(您的自定义类型)
- Pass lists of entities to models for filtering/reasoning
- Model receives JSON representation of entities
- Example: "Filter calendar events related to my trip"
- 将实体列表传递给模型进行过滤/推理
- 模型接收实体的JSON表示
- 示例:"过滤与我的旅行相关的日历事件"
Exposing Entities to Models
向模型暴露实体
Models receive a JSON representation of your entities including:
1. All exposed properties (converted to strings)
swift
struct EventEntity: AppEntity {
var id: UUID
@Property(title: "Title")
var title: String
@Property(title: "Start Date")
var startDate: Date
@Property(title: "End Date")
var endDate: Date
@Property(title: "Notes")
var notes: String?
// All @Property values included in JSON for model
}2. Type display representation (hints what entity represents)
swift
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Calendar Event"3. Display representation (title and subtitle)
swift
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(startDate.formatted())"
)
}模型会接收包含以下内容的实体JSON表示:
1. 所有暴露的属性(转换为字符串)
swift
struct EventEntity: AppEntity {
var id: UUID
@Property(title: "Title")
var title: String
@Property(title: "Start Date")
var startDate: Date
@Property(title: "End Date")
var endDate: Date
@Property(title: "Notes")
var notes: String?
// All @Property values included in JSON for model
}2. 类型显示表示(提示实体代表的内容)
swift
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Calendar Event"3. 显示表示(标题和副标题)
swift
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(startDate.formatted())"
)
}Example JSON sent to model
传递给模型的示例JSON
json
{
"type": "Calendar Event",
"title": "Team Meeting",
"subtitle": "Jan 15, 2025 at 2:00 PM",
"properties": {
"Title": "Team Meeting",
"Start Date": "2025-01-15T14:00:00Z",
"End Date": "2025-01-15T15:00:00Z",
"Notes": "Discuss Q1 roadmap"
}
}json
{
"type": "Calendar Event",
"title": "Team Meeting",
"subtitle": "Jan 15, 2025 at 2:00 PM",
"properties": {
"Title": "Team Meeting",
"Start Date": "2025-01-15T14:00:00Z",
"End Date": "2025-01-15T15:00:00Z",
"Notes": "Discuss Q1 roadmap"
}
}Supporting Rich Text with AttributedString
使用AttributedString支持富文本
Why it matters If your app supports Rich Text content, now is the time to make sure your app intents use the attributed string type for text parameters where appropriate.
重要性 如果您的应用支持富文本内容,现在需要确保您的应用意图在合适的文本参数中使用属性字符串类型。
❌ DON'T: Use plain String
❌ 不要:使用普通String
swift
struct CreateNoteIntent: AppIntent {
@Parameter(title: "Content")
var content: String // Loses formatting from model
}swift
struct CreateNoteIntent: AppIntent {
@Parameter(title: "Content")
var content: String // Loses formatting from model
}✅ DO: Use AttributedString
✅ 要:使用AttributedString
swift
struct CreateNoteIntent: AppIntent {
@Parameter(title: "Content")
var content: AttributedString // Preserves Rich Text
func perform() async throws -> some IntentResult {
let note = Note(content: content) // Rich Text preserved
try await NoteService.shared.save(note)
return .result()
}
}swift
struct CreateNoteIntent: AppIntent {
@Parameter(title: "Content")
var content: AttributedString // Preserves Rich Text
func perform() async throws -> some IntentResult {
let note = Note(content: content) // Rich Text preserved
try await NoteService.shared.save(note)
return .result()
}
}Real-world example from WWDC
WWDC中的真实示例
Bear app's Create Note accepts AttributedString, allowing diary templates from ChatGPT to include:
- Bold headings
- Mood logging tables
- Formatted lists
- All preserved losslessly
Bear应用的创建笔记操作接受AttributedString,允许来自ChatGPT的日记模板包含:
- 粗体标题
- 情绪记录表
- 格式化列表
- 所有内容均无损保留
Automatic Type Conversion
自动类型转换
When Use Model output connects to another action, the runtime automatically converts types:
当使用模型的输出连接到另一个操作时,运行时会自动转换类型:
Example: Boolean for If actions
示例:用于If操作的布尔值
swift
// User's shortcut:
// 1. Get notes created today
// 2. For each note:
// - Use Model: "Is this note related to developing features for Shortcuts?"
// - If [model output] = yes:
// - Add to Shortcuts Projects folderInstead of returning verbose text like "Yes, this note seems to be about developing features for the Shortcuts app", the model automatically returns a Boolean (/) when connected to an If action.
truefalseswift
// User's shortcut:
// 1. Get notes created today
// 2. For each note:
// - Use Model: "Is this note related to developing features for Shortcuts?"
// - If [model output] = yes:
// - Add to Shortcuts Projects folder模型不会返回冗长的文本,如"是的,这条笔记似乎是关于为快捷指令应用开发功能的",当连接到If操作时,模型会自动返回布尔值(/)。
truefalseExplicit output types available
可用的显式输出类型
- Text (AttributedString)
- Number
- Boolean
- Dictionary
- Date
- App Entities
- 文本(AttributedString)
- 数字
- 布尔值
- 字典
- 日期
- App实体
Follow-Up Feature
跟进功能
Enable iterative refinement before passing to next action:
swift
// User runs shortcut:
// 1. Get recipe from Safari
// 2. Use Model: "Extract ingredients list"
// - Follow Up: enabled
// - User types: "Double the recipe"
// - Model adjusts: 800g flour instead of 400g
// 3. Add to Grocery List in Things app在传递到下一个操作前启用迭代优化:
swift
// User runs shortcut:
// 1. Get recipe from Safari
// 2. Use Model: "Extract ingredients list"
// - Follow Up: enabled
// - User types: "Double the recipe"
// - Model adjusts: 800g flour instead of 400g
// 3. Add to Grocery List in Things appWhen to use
使用场景
- Recipe modifications (scale servings, substitute ingredients)
- Content refinement (adjust tone, length, style)
- Data validation (confirm extracted values before saving)
- 食谱修改(调整份量、替换食材)
- 内容优化(调整语气、长度、风格)
- 数据验证(保存前确认提取的值)
IndexedEntity: Automatic Find Actions
IndexedEntity:自动查找操作
Overview
概述
IndexedEntity dramatically reduces boilerplate by auto-generating Find actions from your Spotlight integration. Instead of manually implementing and , adopt IndexedEntity to get:
EntityQueryEntityPropertyQuery- Automatic Find action in Shortcuts
- Property-based filtering
- Search support
- Minimal code required
IndexedEntity 通过从您的聚焦搜索集成中自动生成查找操作,大幅减少样板代码。无需手动实现和,只需采用IndexedEntity即可获得:
EntityQueryEntityPropertyQuery- 快捷指令中的自动查找操作
- 基于属性的过滤
- 搜索支持
- 所需代码极少
Basic Implementation
基本实现
swift
struct EventEntity: AppEntity, IndexedEntity {
var id: UUID
// 1. Properties with indexing keys
@Property(title: "Title", indexingKey: \.eventTitle)
var title: String
@Property(title: "Start Date", indexingKey: \.startDate)
var startDate: Date
@Property(title: "End Date", indexingKey: \.endDate)
var endDate: Date
// 2. Custom key for properties without standard Spotlight attribute
@Property(title: "Notes", customIndexingKey: "eventNotes")
var notes: String?
// Display representation automatically maps to Spotlight
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(startDate.formatted())"
// title → kMDItemTitle
// subtitle → kMDItemDescription
// image → kMDItemContentType (if provided)
)
}
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Event"
}swift
struct EventEntity: AppEntity, IndexedEntity {
var id: UUID
// 1. Properties with indexing keys
@Property(title: "Title", indexingKey: \.eventTitle)
var title: String
@Property(title: "Start Date", indexingKey: \.startDate)
var startDate: Date
@Property(title: "End Date", indexingKey: \.endDate)
var endDate: Date
// 2. Custom key for properties without standard Spotlight attribute
@Property(title: "Notes", customIndexingKey: "eventNotes")
var notes: String?
// Display representation automatically maps to Spotlight
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(startDate.formatted())"
// title → kMDItemTitle
// subtitle → kMDItemDescription
// image → kMDItemContentType (if provided)
)
}
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Event"
}Indexing Key Mapping
索引键映射
Standard Spotlight attribute keys
标准聚焦搜索属性键
swift
// Common Spotlight keys for events
@Property(title: "Title", indexingKey: \.eventTitle)
var title: String
@Property(title: "Start Date", indexingKey: \.startDate)
var startDate: Date
@Property(title: "Location", indexingKey: \.eventLocation)
var location: String?swift
// Common Spotlight keys for events
@Property(title: "Title", indexingKey: \.eventTitle)
var title: String
@Property(title: "Start Date", indexingKey: \.startDate)
var startDate: Date
@Property(title: "Location", indexingKey: \.eventLocation)
var location: String?Custom keys for non-standard attributes
非标准属性的自定义键
swift
@Property(title: "Notes", customIndexingKey: "eventNotes")
var notes: String?
@Property(title: "Attendee Count", customIndexingKey: "attendeeCount")
var attendeeCount: Intswift
@Property(title: "Notes", customIndexingKey: "eventNotes")
var notes: String?
@Property(title: "Attendee Count", customIndexingKey: "attendeeCount")
var attendeeCount: IntAuto-Generated Find Action
自动生成的查找操作
With IndexedEntity conformance, users get this Find action automatically:
通过IndexedEntity一致性,用户会自动获得以下查找操作:
In Shortcuts app
在快捷指令应用中
Find Events where:
- Title contains "Team"
- Start Date is today
- Location is "San Francisco"Find Events where:
- Title contains "Team"
- Start Date is today
- Location is "San Francisco"Without IndexedEntity, you'd need to manually implement
不使用IndexedEntity时,您需要手动实现
- protocol
EnumerableEntityQuery - protocol
EntityPropertyQuery - Property filters for each searchable field
- Search/suggestion logic
With IndexedEntity Just add indexing keys, done!
- 协议
EnumerableEntityQuery - 协议
EntityPropertyQuery - 每个可搜索字段的属性过滤器
- 搜索/建议逻辑
使用IndexedEntity 只需添加索引键,即可完成!
Search Support
搜索支持
Enable string-based search by implementing :
EntityStringQueryswift
extension EventEntityQuery: EntityStringQuery {
func entities(matching string: String) async throws -> [EventEntity] {
return try await EventService.shared.search(query: string)
}
}Or rely on IndexedEntity + Spotlight for automatic search.
通过实现启用基于字符串的搜索:
EntityStringQueryswift
extension EventEntityQuery: EntityStringQuery {
func entities(matching string: String) async throws -> [EventEntity] {
return try await EventService.shared.search(query: string)
}
}或者依赖IndexedEntity + 聚焦搜索实现自动搜索。
Example: Travel Tracking App
示例:旅行追踪应用
Apple's sample code (App Intents Travel Tracking App) demonstrates IndexedEntity:
swift
struct TripEntity: AppEntity, IndexedEntity {
var id: UUID
@Property(title: "Name", indexingKey: \.title)
var name: String
@Property(title: "Start Date", indexingKey: \.startDate)
var startDate: Date
@Property(title: "End Date", indexingKey: \.endDate)
var endDate: Date
@Property(title: "Destination", customIndexingKey: "destination")
var destination: String
// Auto-generated Find Trips action with filters for all properties
}Apple的示例代码(App Intents旅行追踪应用)演示了IndexedEntity的用法:
swift
struct TripEntity: AppEntity, IndexedEntity {
var id: UUID
@Property(title: "Name", indexingKey: \.title)
var name: String
@Property(title: "Start Date", indexingKey: \.startDate)
var startDate: Date
@Property(title: "End Date", indexingKey: \.endDate)
var endDate: Date
@Property(title: "Destination", customIndexingKey: "destination")
var destination: String
// Auto-generated Find Trips action with filters for all properties
}Spotlight on Mac
Mac上的聚焦搜索
Overview
概述
Spotlight on Mac (macOS Sequoia+) allows users to run your app's intents directly from system search. Intents that work in Shortcuts automatically work in Spotlight with proper configuration.
Key principle Spotlight is all about running things quickly. To do that, people need to be able to provide all the information your intent needs to run directly in Spotlight.
Mac上的聚焦搜索(macOS Sequoia+)允许用户直接从系统搜索中运行您应用的意图。只要配置正确,在快捷指令中可用的意图会自动在聚焦搜索中可用。
核心原则 聚焦搜索的核心是快速运行操作。为此,用户需要能够直接在聚焦搜索中提供意图运行所需的所有信息。
Requirements for Spotlight Visibility
聚焦搜索可见性要求
1. Parameter Summary Must Include All Required Parameters
1. 参数摘要必须包含所有必填参数
The parameter summary, which is what people will see in Spotlight UI, must contain all required parameters that don't have a default value.
参数摘要是用户在聚焦搜索UI中会看到的内容,必须包含所有没有默认值的必填参数。
❌ WON'T SHOW in Spotlight
❌ 不会在聚焦搜索中显示
swift
struct CreateEventIntent: AppIntent {
static var title: LocalizedStringResource = "Create Event"
@Parameter(title: "Title")
var title: String
@Parameter(title: "Start Date")
var startDate: Date
@Parameter(title: "End Date")
var endDate: Date
@Parameter(title: "Notes") // Required, no default
var notes: String
static var parameterSummary: some ParameterSummary {
Summary("Create '\(\.$title)' from \(\.$startDate) to \(\.$endDate)")
// Missing 'notes' parameter!
}
}swift
struct CreateEventIntent: AppIntent {
static var title: LocalizedStringResource = "Create Event"
@Parameter(title: "Title")
var title: String
@Parameter(title: "Start Date")
var startDate: Date
@Parameter(title: "End Date")
var endDate: Date
@Parameter(title: "Notes") // Required, no default
var notes: String
static var parameterSummary: some ParameterSummary {
Summary("Create '\(\.$title)' from \(\.$startDate) to \(\.$endDate)")
// Missing 'notes' parameter!
}
}✅ WILL SHOW in Spotlight (Option 1: Make optional)
✅ 会在聚焦搜索中显示(选项1:设为可选)
swift
@Parameter(title: "Notes")
var notes: String? // Optional - can omit from summaryswift
@Parameter(title: "Notes")
var notes: String? // Optional - can omit from summary✅ WILL SHOW in Spotlight (Option 2: Provide default)
✅ 会在聚焦搜索中显示(选项2:提供默认值)
swift
@Parameter(title: "Notes")
var notes: String = "" // Has default - can omit from summaryswift
@Parameter(title: "Notes")
var notes: String = "" // Has default - can omit from summary✅ WILL SHOW in Spotlight (Option 3: Include in summary)
✅ 会在聚焦搜索中显示(选项3:包含在摘要中)
swift
static var parameterSummary: some ParameterSummary {
Summary("Create '\(\.$title)' from \(\.$startDate) to \(\.$endDate)") {
\.$notes // All required params included
}
}swift
static var parameterSummary: some ParameterSummary {
Summary("Create '\(\.$title)' from \(\.$startDate) to \(\.$endDate)") {
\.$notes // All required params included
}
}2. Intent Must Not Be Hidden
2. 意图必须未被隐藏
Intents hidden from Shortcuts won't appear in Spotlight:
swift
// ❌ Hidden from Spotlight
static var isDiscoverable: Bool = false
// ❌ Hidden from Spotlight
static var assistantOnly: Bool = true
// ❌ Hidden from Spotlight
// Intent with no perform() method (widget configuration only)在快捷指令中被隐藏的意图不会出现在聚焦搜索中:
swift
// ❌ Hidden from Spotlight
static var isDiscoverable: Bool = false
// ❌ Hidden from Spotlight
static var assistantOnly: Bool = true
// ❌ Hidden from Spotlight
// Intent with no perform() method (widget configuration only)Providing Suggestions
提供建议
Make parameter filling quick with suggestions:
通过建议让参数填写更快捷:
Option 1: Suggested Entities (Subset of Large List)
选项1:建议实体(大型列表的子集)
swift
struct EventEntityQuery: EntityQuery {
func entities(for identifiers: [UUID]) async throws -> [EventEntity] {
return try await EventService.shared.fetchEvents(ids: identifiers)
}
// Provide upcoming events, not all past/present events
func suggestedEntities() async throws -> [EventEntity] {
return try await EventService.shared.upcomingEvents(limit: 10)
}
}swift
struct EventEntityQuery: EntityQuery {
func entities(for identifiers: [UUID]) async throws -> [EventEntity] {
return try await EventService.shared.fetchEvents(ids: identifiers)
}
// Provide upcoming events, not all past/present events
func suggestedEntities() async throws -> [EventEntity] {
return try await EventService.shared.upcomingEvents(limit: 10)
}
}Option 2: All Entities (Small, Bounded List)
选项2:所有实体(小型、有限列表)
swift
struct TimezoneQuery: EnumerableEntityQuery {
func allEntities() async throws -> [TimezoneEntity] {
// Small list - provide all
return TimezoneEntity.allTimezones
}
}Use suggested entities when List is large or unbounded (calendar events, notes, contacts)
Use all entities when List is small and bounded (timezones, priority levels, categories)
swift
struct TimezoneQuery: EnumerableEntityQuery {
func allEntities() async throws -> [TimezoneEntity] {
// Small list - provide all
return TimezoneEntity.allTimezones
}
}使用建议实体的场景 列表较大或无界(日历事件、笔记、联系人)
使用所有实体的场景 列表较小且有限(时区、优先级、分类)
On-Screen Content Tagging
屏幕内容标记
Suggest currently active content:
swift
// In your detail view controller
func showEventDetail(_ event: Event) {
let activity = NSUserActivity(activityType: "com.myapp.viewEvent")
activity.persistentIdentifier = event.id.uuidString
// Spotlight suggests this event for parameters
activity.appEntityIdentifier = event.id.uuidString
userActivity = activity
}For more details on on-screen content tagging, see the "Exploring New Advances in App Intents" session.
建议当前活跃的内容:
swift
// In your detail view controller
func showEventDetail(_ event: Event) {
let activity = NSUserActivity(activityType: "com.myapp.viewEvent")
activity.persistentIdentifier = event.id.uuidString
// Spotlight suggests this event for parameters
activity.appEntityIdentifier = event.id.uuidString
userActivity = activity
}有关屏幕内容标记的更多详细信息,请参阅"探索App Intents的新进展"会话。
Search Beyond Suggestions
超越建议的搜索
Basic filtering (automatic):
If you provide suggestions, Spotlight automatically filters them as user types.
Deep search (requires implementation):
For searching beyond suggestions:
基础过滤(自动):
如果您提供了建议,聚焦搜索会在用户输入时自动过滤这些建议。
深度搜索(需要实现):
要超越建议进行搜索:
Option 1: EntityStringQuery
选项1:EntityStringQuery
swift
extension EventQuery: EntityStringQuery {
func entities(matching string: String) async throws -> [EventEntity] {
return try await EventService.shared.search(query: string)
}
}swift
extension EventQuery: EntityStringQuery {
func entities(matching string: String) async throws -> [EventEntity] {
return try await EventService.shared.search(query: string)
}
}Option 2: IndexedEntity
选项2:IndexedEntity
swift
struct EventEntity: AppEntity, IndexedEntity {
// Spotlight search automatically supported
}swift
struct EventEntity: AppEntity, IndexedEntity {
// Spotlight search automatically supported
}Background vs Foreground Intents
后台与前台意图
Pattern: Paired Intents with opensIntent
模式:使用opensIntent的配对意图
swift
// Background intent - runs without opening app
struct CreateEventIntent: AppIntent {
static var openAppWhenRun: Bool = false
@Parameter(title: "Title")
var title: String
@Parameter(title: "Start Date")
var startDate: Date
func perform() async throws -> some IntentResult {
let event = try await EventService.shared.createEvent(
title: title,
startDate: startDate
)
// Optionally open app to view created event
return .result(
value: EventEntity(from: event),
opensIntent: OpenEventIntent(event: EventEntity(from: event))
)
}
}
// Foreground intent - opens app to specific event
struct OpenEventIntent: AppIntent {
static var openAppWhenRun: Bool = true
@Parameter(title: "Event")
var event: EventEntity
func perform() async throws -> some IntentResult {
await MainActor.run {
EventCoordinator.shared.showEvent(id: event.id)
}
return .result()
}
}swift
// Background intent - runs without opening app
struct CreateEventIntent: AppIntent {
static var openAppWhenRun: Bool = false
@Parameter(title: "Title")
var title: String
@Parameter(title: "Start Date")
var startDate: Date
func perform() async throws -> some IntentResult {
let event = try await EventService.shared.createEvent(
title: title,
startDate: startDate
)
// Optionally open app to view created event
return .result(
value: EventEntity(from: event),
opensIntent: OpenEventIntent(event: EventEntity(from: event))
)
}
}
// Foreground intent - opens app to specific event
struct OpenEventIntent: AppIntent {
static var openAppWhenRun: Bool = true
@Parameter(title: "Event")
var event: EventEntity
func perform() async throws -> some IntentResult {
await MainActor.run {
EventCoordinator.shared.showEvent(id: event.id)
}
return .result()
}
}User experience
用户体验
- User runs "Create Event" in Spotlight (background)
- Event created without opening app
- Spotlight shows "Open in App" button (opensIntent)
- User taps button → App opens to event detail
- 用户在聚焦搜索中运行"创建事件"(后台)
- 无需打开应用即可创建事件
- 聚焦搜索显示"在应用中打开"按钮(opensIntent)
- 用户点击按钮 → 应用打开到事件详情页
Predictable Intent Protocol
PredictableIntent协议
Enable Spotlight suggestions based on usage patterns:
swift
struct OrderCoffeeIntent: AppIntent, PredictableIntent {
static var title: LocalizedStringResource = "Order Coffee"
@Parameter(title: "Coffee Type")
var coffeeType: CoffeeType
@Parameter(title: "Size")
var size: CoffeeSize
func perform() async throws -> some IntentResult {
// Order logic
return .result()
}
}Spotlight learns when/how user runs this intent and surfaces suggestions proactively.
基于使用模式启用聚焦搜索建议:
swift
struct OrderCoffeeIntent: AppIntent, PredictableIntent {
static var title: LocalizedStringResource = "Order Coffee"
@Parameter(title: "Coffee Type")
var coffeeType: CoffeeType
@Parameter(title: "Size")
var size: CoffeeSize
func perform() async throws -> some IntentResult {
// Order logic
return .result()
}
}聚焦搜索会学习用户运行此意图的时间和方式,并主动显示建议。
Automations on Mac
Mac上的自动化
Overview
概述
Personal Automations arrive on macOS (macOS Sequoia+) with Mac-specific triggers:
个人自动化 随macOS Sequoia+登陆Mac,带来Mac专属的触发条件:
New Mac Automation Types
新的Mac自动化类型
- Folder Automation — Trigger when files added/removed from folder
- External Drive Automation — Trigger when drive connected/disconnected
- Time of Day (from iOS)
- Bluetooth (from iOS)
- And more...
Example use case Invoice processing shortcut runs automatically every time a new invoice is added to ~/Documents/Invoices folder.
- 文件夹自动化 — 当文件添加到/从文件夹中移除时触发
- 外部驱动器自动化 — 当驱动器连接/断开时触发
- 时间触发(源自iOS)
- 蓝牙触发(源自iOS)
- 更多...
示例用例 每次有新发票添加到~/Documents/Invoices文件夹时,自动运行发票处理快捷指令。
Automatic Availability
自动可用性
As long as your intent is available on macOS, they will also be available to use in Shortcuts to run as a part of Automations on Mac. This includes iOS apps that are installable on macOS.
No additional code required — your existing intents work in automations automatically.
只要您的意图在Mac上可用,它们也将可在快捷指令中用作Mac自动化的一部分。这包括可在Mac上安装的iOS应用。
无需额外代码 — 您现有的意图会自动在自动化中工作。
Platform Support
平台支持
swift
struct ProcessInvoiceIntent: AppIntent {
static var title: LocalizedStringResource = "Process Invoice"
// Available on macOS automatically
// Also works: iOS apps installed on Mac (Catalyst, Mac Catalyst)
@Parameter(title: "Invoice")
var invoice: FileEntity
func perform() async throws -> some IntentResult {
// Extract data, add to spreadsheet, etc.
return .result()
}
}swift
struct ProcessInvoiceIntent: AppIntent {
static var title: LocalizedStringResource = "Process Invoice"
// Available on macOS automatically
// Also works: iOS apps installed on Mac (Catalyst, Mac Catalyst)
@Parameter(title: "Invoice")
var invoice: FileEntity
func perform() async throws -> some IntentResult {
// Extract data, add to spreadsheet, etc.
return .result()
}
}Additional System Integration Points
其他系统集成点
With automations, your intents are now accessible from:
- Siri — Voice commands
- Shortcuts app — Manual workflows
- Spotlight — Quick actions
- Automations — Triggered workflows
- Action Button — Hardware trigger (Apple Watch Ultra)
- Control Center — Quick controls
- Widgets — Interactive elements
- Live Activities — Dynamic Island
通过自动化,您的意图现在可从以下位置访问:
- Siri — 语音命令
- 快捷指令应用 — 手动工作流
- 聚焦搜索 — 快速操作
- 自动化 — 触发式工作流
- 操作按钮 — 硬件触发(Apple Watch Ultra)
- 控制中心 — 快速控件
- 小组件 — 交互元素
- 实时活动 — 灵动岛
Assistant Schemas (Pre-built Intents)
助手模式(预构建意图)
Apple provides pre-built schemas for common app categories:
Apple为常见应用类别提供预构建的模式:
Books App Example
图书应用示例
swift
import AppIntents
import BooksIntents
struct OpenBookIntent: BooksOpenBookIntent {
@Parameter(title: "Book")
var target: BookEntity
func perform() async throws -> some IntentResult {
await MainActor.run {
BookReader.shared.open(book: target)
}
return .result()
}
}swift
import AppIntents
import BooksIntents
struct OpenBookIntent: BooksOpenBookIntent {
@Parameter(title: "Book")
var target: BookEntity
func perform() async throws -> some IntentResult {
await MainActor.run {
BookReader.shared.open(book: target)
}
return .result()
}
}Available Assistant Schemas
可用的助手模式
- BooksIntents — Navigate pages, open books, play audiobooks, search
- BrowserIntents — Bookmark tabs, clear history, manage windows
- CameraIntents — Capture modes, device switching, start/stop
- EmailIntents — Draft management, reply, forward, archive
- PhotosIntents — Album/asset management, editing, filtering
- PresentationsIntents — Slide creation, media insertion, playback
- SpreadsheetsIntents — Sheet management, content addition
- DocumentsIntents — File management, page manipulation, search
- BooksIntents — 翻页、打开图书、播放有声书、搜索
- BrowserIntents — 书签标签、清除历史、管理窗口
- CameraIntents — 拍摄模式、设备切换、启动/停止
- EmailIntents — 草稿管理、回复、转发、归档
- PhotosIntents — 相册/资源管理、编辑、过滤
- PresentationsIntents — 幻灯片创建、媒体插入、播放
- SpreadsheetsIntents — 表格管理、内容添加
- DocumentsIntents — 文件管理、页面操作、搜索
Testing & Debugging
测试与调试
Testing with Shortcuts App
使用快捷指令应用测试
-
Add intent to Shortcuts:
- Open Shortcuts app
- Tap "+" to create new shortcut
- Search for your app name
- Select your intent
-
Test parameter resolution:
- Fill in parameters
- Run shortcut
- Check Xcode console for logs
-
Test with Siri:
- "Hey Siri, [your intent name]"
- Siri should prompt for parameters
- Verify dialog text and results
-
将意图添加到快捷指令:
- 打开快捷指令应用
- 点击"+"创建新快捷指令
- 搜索您的应用名称
- 选择您的意图
-
测试参数解析:
- 填写参数
- 运行快捷指令
- 查看Xcode控制台中的日志
-
使用Siri测试:
- "嘿Siri,[您的意图名称]"
- Siri应提示输入参数
- 验证对话框文本和结果
Xcode Intent Testing
Xcode意图测试
swift
// In your app target, not tests
#if DEBUG
extension OrderSoupIntent {
static func testIntent() async throws {
let intent = OrderSoupIntent()
intent.soup = SoupEntity(id: "1", name: "Tomato", price: 8.99)
intent.quantity = 2
let result = try await intent.perform()
print("Result: \(result)")
}
}
#endifswift
// In your app target, not tests
#if DEBUG
extension OrderSoupIntent {
static func testIntent() async throws {
let intent = OrderSoupIntent()
intent.soup = SoupEntity(id: "1", name: "Tomato", price: 8.99)
intent.quantity = 2
let result = try await intent.perform()
print("Result: \(result)")
}
}
#endifCommon Debugging Issues
常见调试问题
Issue 1: Intent not appearing in Shortcuts
问题1:意图未在快捷指令中显示
swift
// ❌ Problem: isDiscoverable = false or missing
struct MyIntent: AppIntent {
// Missing isDiscoverable
}
// ✅ Solution: Make discoverable
struct MyIntent: AppIntent {
static var isDiscoverable: Bool = true
}swift
// ❌ Problem: isDiscoverable = false or missing
struct MyIntent: AppIntent {
// Missing isDiscoverable
}
// ✅ Solution: Make discoverable
struct MyIntent: AppIntent {
static var isDiscoverable: Bool = true
}Issue 2: Parameter not resolving
问题2:参数未解析
swift
// ❌ Problem: Missing defaultQuery
struct ProductEntity: AppEntity {
var id: String
// Missing defaultQuery
}
// ✅ Solution: Add query
struct ProductEntity: AppEntity {
var id: String
static var defaultQuery = ProductQuery()
}swift
// ❌ Problem: Missing defaultQuery
struct ProductEntity: AppEntity {
var id: String
// Missing defaultQuery
}
// ✅ Solution: Add query
struct ProductEntity: AppEntity {
var id: String
static var defaultQuery = ProductQuery()
}Issue 3: Intent crashes in background
问题3:意图在后台崩溃
swift
// ❌ Problem: Accessing MainActor from background
func perform() async throws -> some IntentResult {
UIApplication.shared.open(url) // Crash! MainActor only
return .result()
}
// ✅ Solution: Use MainActor or openAppWhenRun
func perform() async throws -> some IntentResult {
await MainActor.run {
UIApplication.shared.open(url)
}
return .result()
}swift
// ❌ Problem: Accessing MainActor from background
func perform() async throws -> some IntentResult {
UIApplication.shared.open(url) // Crash! MainActor only
return .result()
}
// ✅ Solution: Use MainActor or openAppWhenRun
func perform() async throws -> some IntentResult {
await MainActor.run {
UIApplication.shared.open(url)
}
return .result()
}Issue 4: Entity query returns empty results
问题4:实体查询返回空结果
swift
// ❌ Problem: entities(for:) not implemented
struct BookQuery: EntityQuery {
// Missing entities(for:) implementation
}
// ✅ Solution: Implement required methods
struct BookQuery: EntityQuery {
func entities(for identifiers: [UUID]) async throws -> [BookEntity] {
return try await BookService.shared.fetchBooks(ids: identifiers)
}
func suggestedEntities() async throws -> [BookEntity] {
return try await BookService.shared.recentBooks(limit: 10)
}
}swift
// ❌ Problem: entities(for:) not implemented
struct BookQuery: EntityQuery {
// Missing entities(for:) implementation
}
// ✅ Solution: Implement required methods
struct BookQuery: EntityQuery {
func entities(for identifiers: [UUID]) async throws -> [BookEntity] {
return try await BookService.shared.fetchBooks(ids: identifiers)
}
func suggestedEntities() async throws -> [BookEntity] {
return try await BookService.shared.recentBooks(limit: 10)
}
}Best Practices
最佳实践
1. Intent Naming
1. 意图命名
❌ DON'T: Generic or unclear
❌ 不要:通用或不明确
swift
static var title: LocalizedStringResource = "Do Thing"
static var title: LocalizedStringResource = "Process"swift
static var title: LocalizedStringResource = "Do Thing"
static var title: LocalizedStringResource = "Process"✅ DO: Verb-noun, specific
✅ 要:动宾结构、具体明确
swift
static var title: LocalizedStringResource = "Send Message"
static var title: LocalizedStringResource = "Book Appointment"
static var title: LocalizedStringResource = "Start Workout"swift
static var title: LocalizedStringResource = "Send Message"
static var title: LocalizedStringResource = "Book Appointment"
static var title: LocalizedStringResource = "Start Workout"2. Parameter Summary
2. 参数摘要
❌ DON'T: Technical or confusing
❌ 不要:技术化或令人困惑
swift
static var parameterSummary: some ParameterSummary {
Summary("Execute \(\.$action) with \(\.$target)")
}swift
static var parameterSummary: some ParameterSummary {
Summary("Execute \(\.$action) with \(\.$target)")
}✅ DO: Natural language
✅ 要:自然语言
swift
static var parameterSummary: some ParameterSummary {
Summary("Send \(\.$message) to \(\.$contact)")
}
// Siri: "Send 'Hello' to John"swift
static var parameterSummary: some ParameterSummary {
Summary("Send \(\.$message) to \(\.$contact)")
}
// Siri: "Send 'Hello' to John"3. Error Messages
3. 错误消息
❌ DON'T: Technical jargon
❌ 不要:技术术语
swift
throw MyError.validationFailed("Invalid parameter state")swift
throw MyError.validationFailed("Invalid parameter state")✅ DO: User-friendly
✅ 要:用户友好
swift
throw MyError.outOfStock("Sorry, this item is currently unavailable")swift
throw MyError.outOfStock("Sorry, this item is currently unavailable")4. Entity Suggestions
4. 实体建议
❌ DON'T: Return all entities
❌ 不要:返回所有实体
swift
func suggestedEntities() async throws -> [TaskEntity] {
return try await TaskService.shared.allTasks() // Could be thousands!
}swift
func suggestedEntities() async throws -> [TaskEntity] {
return try await TaskService.shared.allTasks() // Could be thousands!
}✅ DO: Limit to recent/relevant
✅ 要:限制为近期/相关实体
swift
func suggestedEntities() async throws -> [TaskEntity] {
return try await TaskService.shared.recentTasks(limit: 10)
}swift
func suggestedEntities() async throws -> [TaskEntity] {
return try await TaskService.shared.recentTasks(limit: 10)
}5. Async Operations
5. 异步操作
❌ DON'T: Block main thread
❌ 不要:阻塞主线程
swift
func perform() async throws -> some IntentResult {
let data = URLSession.shared.synchronousDataTask(url) // Blocks!
return .result()
}swift
func perform() async throws -> some IntentResult {
let data = URLSession.shared.synchronousDataTask(url) // Blocks!
return .result()
}✅ DO: Use async/await
✅ 要:使用async/await
swift
func perform() async throws -> some IntentResult {
let data = try await URLSession.shared.data(from: url)
return .result()
}swift
func perform() async throws -> some IntentResult {
let data = try await URLSession.shared.data(from: url)
return .result()
}Real-World Examples
真实示例
Example 1: Start Workout Intent
示例1:开始锻炼意图
swift
struct StartWorkoutIntent: AppIntent {
static var title: LocalizedStringResource = "Start Workout"
static var description: IntentDescription = "Starts a new workout session"
static var openAppWhenRun: Bool = true
@Parameter(title: "Workout Type")
var workoutType: WorkoutType
@Parameter(title: "Duration (minutes)")
var duration: Int?
static var parameterSummary: some ParameterSummary {
Summary("Start \(\.$workoutType)") {
\.$duration
}
}
func perform() async throws -> some IntentResult {
let workout = Workout(
type: workoutType,
duration: duration.map { TimeInterval($0 * 60) }
)
await MainActor.run {
WorkoutCoordinator.shared.start(workout)
}
return .result(
dialog: "Starting \(workoutType.displayName) workout"
)
}
}
enum WorkoutType: String, AppEnum {
case running
case cycling
case swimming
case yoga
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Workout Type"
static var caseDisplayRepresentations: [WorkoutType: DisplayRepresentation] = [
.running: "Running",
.cycling: "Cycling",
.swimming: "Swimming",
.yoga: "Yoga"
]
var displayName: String {
switch self {
case .running: return "running"
case .cycling: return "cycling"
case .swimming: return "swimming"
case .yoga: return "yoga"
}
}
}swift
struct StartWorkoutIntent: AppIntent {
static var title: LocalizedStringResource = "Start Workout"
static var description: IntentDescription = "Starts a new workout session"
static var openAppWhenRun: Bool = true
@Parameter(title: "Workout Type")
var workoutType: WorkoutType
@Parameter(title: "Duration (minutes)")
var duration: Int?
static var parameterSummary: some ParameterSummary {
Summary("Start \(\.$workoutType)") {
\.$duration
}
}
func perform() async throws -> some IntentResult {
let workout = Workout(
type: workoutType,
duration: duration.map { TimeInterval($0 * 60) }
)
await MainActor.run {
WorkoutCoordinator.shared.start(workout)
}
return .result(
dialog: "Starting \(workoutType.displayName) workout"
)
}
}
enum WorkoutType: String, AppEnum {
case running
case cycling
case swimming
case yoga
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Workout Type"
static var caseDisplayRepresentations: [WorkoutType: DisplayRepresentation] = [
.running: "Running",
.cycling: "Cycling",
.swimming: "Swimming",
.yoga: "Yoga"
]
var displayName: String {
switch self {
case .running: return "running"
case .cycling: return "cycling"
case .swimming: return "swimming"
case .yoga: return "yoga"
}
}
}Example 2: Add Task with Entity Query
示例2:添加带实体查询的任务
swift
struct AddTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Add Task"
static var description: IntentDescription = "Creates a new task"
static var isDiscoverable: Bool = true
@Parameter(title: "Title")
var title: String
@Parameter(title: "List")
var list: TaskListEntity?
@Parameter(title: "Due Date")
var dueDate: Date?
static var parameterSummary: some ParameterSummary {
Summary("Add '\(\.$title)'") {
\.$list
\.$dueDate
}
}
func perform() async throws -> some IntentResult {
let task = try await TaskService.shared.createTask(
title: title,
list: list?.id,
dueDate: dueDate
)
return .result(
value: TaskEntity(from: task),
dialog: "Task '\(title)' added"
)
}
}
struct TaskListEntity: AppEntity {
var id: UUID
var name: String
var color: String
static var typeDisplayRepresentation: TypeDisplayRepresentation = "List"
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)",
image: .init(systemName: "list.bullet")
)
}
static var defaultQuery = TaskListQuery()
}
struct TaskListQuery: EntityQuery, EntityStringQuery {
func entities(for identifiers: [UUID]) async throws -> [TaskListEntity] {
return try await TaskService.shared.fetchLists(ids: identifiers)
}
func suggestedEntities() async throws -> [TaskListEntity] {
// Provide user's favorite lists
return try await TaskService.shared.favoriteLists(limit: 5)
}
func entities(matching string: String) async throws -> [TaskListEntity] {
return try await TaskService.shared.searchLists(query: string)
}
}swift
struct AddTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Add Task"
static var description: IntentDescription = "Creates a new task"
static var isDiscoverable: Bool = true
@Parameter(title: "Title")
var title: String
@Parameter(title: "List")
var list: TaskListEntity?
@Parameter(title: "Due Date")
var dueDate: Date?
static var parameterSummary: some ParameterSummary {
Summary("Add '\(\.$title)'") {
\.$list
\.$dueDate
}
}
func perform() async throws -> some IntentResult {
let task = try await TaskService.shared.createTask(
title: title,
list: list?.id,
dueDate: dueDate
)
return .result(
value: TaskEntity(from: task),
dialog: "Task '\(title)' added"
)
}
}
struct TaskListEntity: AppEntity {
var id: UUID
var name: String
var color: String
static var typeDisplayRepresentation: TypeDisplayRepresentation = "List"
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)",
image: .init(systemName: "list.bullet")
)
}
static var defaultQuery = TaskListQuery()
}
struct TaskListQuery: EntityQuery, EntityStringQuery {
func entities(for identifiers: [UUID]) async throws -> [TaskListEntity] {
return try await TaskService.shared.fetchLists(ids: identifiers)
}
func suggestedEntities() async throws -> [TaskListEntity] {
// Provide user's favorite lists
return try await TaskService.shared.favoriteLists(limit: 5)
}
func entities(matching string: String) async throws -> [TaskListEntity] {
return try await TaskService.shared.searchLists(query: string)
}
}App Intents Checklist
App Intents 检查清单
Before Submitting to App Store
提交至App Store前
- ☐ All intents have clear, localized titles and descriptions
- ☐ Parameter summaries use natural language phrasing
- ☐ Error messages are user-friendly, not technical
- ☐ Authentication policies match data sensitivity
- ☐ Entity queries return reasonable suggestion counts (< 20)
- ☐ Intents marked appear in Shortcuts
isDiscoverable - ☐ Destructive actions request confirmation
- ☐ Background intents don't access MainActor
- ☐ Foreground intents set
openAppWhenRun = true - ☐ Entity shows meaningful info
displayRepresentation - ☐ Tested with Siri voice commands
- ☐ Tested in Shortcuts app
- ☐ Tested with different parameter combinations
- ☐ Verified localization for all supported languages
- ☐ 所有意图都有清晰的本地化标题和描述
- ☐ 参数摘要使用自然语言表述
- ☐ 错误消息对用户友好,无技术术语
- ☐ 身份验证策略与数据敏感度匹配
- ☐ 实体查询返回合理的建议数量(<20)
- ☐ 标记为的意图在快捷指令中显示
isDiscoverable - ☐ 破坏性操作会请求确认
- ☐ 后台意图不访问MainActor
- ☐ 前台意图设置
openAppWhenRun = true - ☐ 实体的显示有意义的信息
displayRepresentation - ☐ 已使用Siri语音命令测试
- ☐ 已在快捷指令应用中测试
- ☐ 已测试不同的参数组合
- ☐ 已验证所有支持语言的本地化
Resources
资源
WWDC: 244, 275, 260
Docs: /appintents, /appintents/appintent, /appintents/appentity
Skills: axiom-app-shortcuts-ref, axiom-core-spotlight-ref, axiom-app-discoverability
Remember App Intents are how users interact with your app through Siri, Shortcuts, and system features. Well-designed intents feel like a natural extension of your app's functionality and provide value across Apple's ecosystem.
WWDC: 244, 275, 260
Docs: /appintents, /appintents/appintent, /appintents/appentity
Skills: axiom-app-shortcuts-ref, axiom-core-spotlight-ref, axiom-app-discoverability
请记住 App Intents是用户通过Siri、快捷指令和系统功能与您应用交互的方式。设计良好的意图会成为您应用功能的自然延伸,并在Apple生态系统中为用户提供价值。