Apple Intelligence

迎接 AI 时代。通过 App Intents 框架将应用能力深度集成至 Siri 与系统级人工智能服务。

接入系统级 AI 时代的必修课。让你的 App 与 Siri、Spotlight、快捷指令深度集成,提供智能化用户体验。


1. Apple Intelligence 概述

Apple Intelligence 是 Apple 在 iOS 18/macOS 15 推出的端侧 AI 系统,让 App 能够:

  • 与 Siri 深度集成
  • 出现在 Spotlight 搜索中
  • 被快捷指令调用
  • 支持 Action Button 触发
  • 在控制中心显示

2. App Intents 框架

App Intents 是定义 App 能力的现代化框架,取代了旧的 SiriKit Intents。

基础 Intent

import AppIntents

struct OpenArticleIntent: AppIntent {
    // 显示名称
    static var title: LocalizedStringResource = "打开文章"
    
    // 描述
    static var description = IntentDescription("打开指定的文章")
    
    // 参数
    @Parameter(title: "文章标题")
    var articleTitle: String
    
    // 执行逻辑
    func perform() async throws -> some IntentResult {
        // 打开文章的逻辑
        ArticleManager.shared.open(title: articleTitle)
        return .result()
    }
}

带返回值的 Intent

struct GetWeatherIntent: AppIntent {
    static var title: LocalizedStringResource = "获取天气"
    
    @Parameter(title: "城市")
    var city: String
    
    func perform() async throws -> some IntentResult & ReturnsValue<String> {
        let weather = try await WeatherService.fetch(city: city)
        return .result(value: "当前温度: \(weather.temperature)°C")
    }
}

打开 App 的 Intent

struct OpenAppIntent: AppIntent, OpenIntent {
    static var title: LocalizedStringResource = "打开我的应用"
    
    @Parameter(title: "目标页面")
    var target: NavigationTarget?
    
    func perform() async throws -> some IntentResult {
        // 导航到指定页面
        await NavigationManager.shared.navigate(to: target)
        return .result()
    }
}

// 定义导航目标
enum NavigationTarget: String, AppEnum {
    case home = "首页"
    case settings = "设置"
    case profile = "个人中心"
    
    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "页面")
    static var caseDisplayRepresentations: [NavigationTarget: DisplayRepresentation] = [
        .home: "首页",
        .settings: "设置",
        .profile: "个人中心"
    ]
}

3. App Shortcuts

让 Intent 在系统中可被发现。

定义 Shortcuts

struct MyAppShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: OpenArticleIntent(),
            phrases: [
                "打开 \(.applicationName) 的文章",
                "在 \(.applicationName) 中查看 \(\.$articleTitle)",
                "用 \(.applicationName) 阅读文章"
            ],
            shortTitle: "打开文章",
            systemImageName: "doc.text"
        )
        
        AppShortcut(
            intent: GetWeatherIntent(),
            phrases: [
                "用 \(.applicationName) 查天气",
                "\(.applicationName) \(\.$city) 天气怎么样"
            ],
            shortTitle: "查询天气",
            systemImageName: "cloud.sun"
        )
    }
}

注册 ShortcutsProvider

在 App 入口注册:

@main
struct MyApp: App {
    init() {
        // 更新 Shortcuts
        MyAppShortcuts.updateAppShortcutParameters()
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

4. 参数类型

基础类型

@Parameter(title: "数量")
var count: Int

@Parameter(title: "名称")
var name: String

@Parameter(title: "日期")
var date: Date

@Parameter(title: "启用")
var isEnabled: Bool

可选参数

@Parameter(title: "标签", default: nil)
var tag: String?

枚举参数

enum Priority: String, AppEnum {
    case low, medium, high
    
    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "优先级")
    
    static var caseDisplayRepresentations: [Priority: DisplayRepresentation] = [
        .low: "低",
        .medium: "中",
        .high: "高"
    ]
}

@Parameter(title: "优先级")
var priority: Priority

实体参数

struct ArticleEntity: AppEntity {
    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "文章")
    
    var id: UUID
    var title: String
    var author: String
    
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(title)", subtitle: "作者: \(author)")
    }
    
    static var defaultQuery = ArticleQuery()
}

struct ArticleQuery: EntityQuery {
    func entities(for identifiers: [UUID]) async throws -> [ArticleEntity] {
        // 从数据库获取文章
        ArticleStore.shared.articles(for: identifiers)
    }
    
    func suggestedEntities() async throws -> [ArticleEntity] {
        // 返回建议的文章
        ArticleStore.shared.recentArticles
    }
}

5. Spotlight 集成

CSSearchableItem

让 App 内容出现在 Spotlight 搜索中:

import CoreSpotlight

func indexArticle(_ article: Article) {
    let attributeSet = CSSearchableItemAttributeSet(contentType: .text)
    attributeSet.title = article.title
    attributeSet.contentDescription = article.summary
    attributeSet.thumbnailData = article.thumbnailData
    attributeSet.keywords = article.tags
    
    let item = CSSearchableItem(
        uniqueIdentifier: article.id.uuidString,
        domainIdentifier: "com.myapp.articles",
        attributeSet: attributeSet
    )
    
    // 设置过期时间
    item.expirationDate = Date().addingTimeInterval(30 * 24 * 60 * 60) // 30天
    
    CSSearchableIndex.default().indexSearchableItems([item]) { error in
        if let error {
            print("索引失败: \(error)")
        }
    }
}

// 删除索引
func removeArticleFromIndex(_ articleId: UUID) {
    CSSearchableIndex.default().deleteSearchableItems(
        withIdentifiers: [articleId.uuidString]
    ) { error in
        if let error {
            print("删除索引失败: \(error)")
        }
    }
}

处理 Spotlight 点击

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        if userActivity.activityType == CSSearchableItemActionType,
           let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String {
            // 导航到对应内容
            navigateToArticle(id: identifier)
        }
    }
}

6. Siri 语音交互

SiriTipView

显示 Siri 调用提示:

import AppIntents

struct ArticleView: View {
    var body: some View {
        VStack {
            // 文章内容...
            
            // Siri 提示
            SiriTipView(intent: OpenArticleIntent())
                .siriTipViewStyle(.automatic)
        }
    }
}

自定义语音响应

struct ReadArticleIntent: AppIntent {
    static var title: LocalizedStringResource = "朗读文章"
    
    @Parameter(title: "文章")
    var article: ArticleEntity
    
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let content = article.content
        return .result(dialog: "正在为您朗读: \(article.title)")
    }
}

7. 控制中心 Widget

iOS 18 支持在控制中心添加 App 控件:

import WidgetKit
import AppIntents

struct ToggleFeatureControl: ControlWidget {
    static let kind = "com.myapp.togglefeature"
    
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: Self.kind) {
            ControlWidgetButton(action: ToggleFeatureIntent()) {
                Label("快速操作", systemImage: "bolt.fill")
            }
        }
        .displayName("快速操作")
        .description("一键执行常用操作")
    }
}

struct ToggleFeatureIntent: AppIntent {
    static var title: LocalizedStringResource = "切换功能"
    
    func perform() async throws -> some IntentResult {
        FeatureManager.shared.toggle()
        return .result()
    }
}

8. Action Button 支持

让 iPhone 15 Pro 的 Action Button 调用你的 App:

struct QuickCaptureIntent: AppIntent {
    static var title: LocalizedStringResource = "快速捕捉"
    static var isDiscoverable = true
    
    // 标记为 Action Button 可用
    static var authenticationPolicy: IntentAuthenticationPolicy = .alwaysAllowed
    
    func perform() async throws -> some IntentResult & OpensIntent {
        // 打开 App 并开始捕捉
        return .result(opensIntent: OpenCaptureIntent())
    }
}

9. Focus 滤镜

在专注模式下过滤 App 内容:

import AppIntents

struct WorkFocusFilter: SetFocusFilterIntent {
    static var title: LocalizedStringResource = "工作模式"
    static var description = IntentDescription("只显示工作相关内容")
    
    @Parameter(title: "显示工作内容")
    var showWorkContent: Bool
    
    func perform() async throws -> some IntentResult {
        ContentFilter.shared.workModeEnabled = showWorkContent
        return .result()
    }
}

10. 最佳实践

✅ 推荐

// 1. 使用清晰的 Intent 命名
struct CreateReminderIntent: AppIntent {
    static var title: LocalizedStringResource = "创建提醒"
    // ...
}

// 2. 提供多种触发短语
AppShortcut(
    intent: CreateReminderIntent(),
    phrases: [
        "用 \(.applicationName) 创建提醒",
        "在 \(.applicationName) 添加提醒",
        "\(.applicationName) 提醒我 \(\.$content)"
    ]
)

// 3. 处理错误
func perform() async throws -> some IntentResult {
    guard let data = try? await fetchData() else {
        throw IntentError.message("无法获取数据")
    }
    return .result()
}

// 4. 使用 @MainActor 更新 UI
@MainActor
func perform() async throws -> some IntentResult {
    // 安全地更新 UI
}

❌ 避免

// 1. 不要执行长时间操作
func perform() async throws -> some IntentResult {
    // ❌ 超过 10 秒会被系统终止
    await veryLongOperation()
}

// 2. 不要依赖 App 状态
func perform() async throws -> some IntentResult {
    // ❌ Intent 可能在 App 未运行时执行
    guard let viewController = window?.rootViewController else { return }
}

// 3. 不要暴露敏感信息
struct BadIntent: AppIntent {
    // ❌ 不要在 Siri 响应中返回密码等敏感信息
    func perform() async throws -> some ProvidesDialog {
        return .result(dialog: "密码是: \(password)")
    }
}

11. 调试技巧

测试 Shortcuts

  1. 打开 快捷指令 App
  2. 创建新快捷指令
  3. 搜索你的 App Intent
  4. 运行测试

Siri 测试

# 在模拟器中使用 Siri
# 长按 Home 键或说 "Hey Siri"

# 查看 Intent 日志
xcrun simctl spawn booted log stream --predicate 'subsystem == "com.apple.Intents"'

调试日志

import os.log

struct MyIntent: AppIntent {
    static let logger = Logger(subsystem: "com.myapp", category: "Intents")
    
    func perform() async throws -> some IntentResult {
        Self.logger.info("Intent 开始执行")
        // ...
        Self.logger.info("Intent 执行完成")
        return .result()
    }
}