App Intents — AppEnum
This article is extracted from the chat log with AI. Please identify it with caution.

AppEnum 是 App Intents 框架中一个非常重要的协议,它允许你定义自定义的枚举类型。这些枚举类型可以作为你的 AppIntent(应用意图)中的参数类型,或者在 AppEntity(应用实体)中作为属性的类型。使用 AppEnum 可以为用户提供一组固定的、易于理解的选项,从而增强 Siri、快捷指令以及其他系统服务与你应用交互的体验。

一、基本概念 #

  • 是什么? AppEnum 是一个协议,你需要让你的 Swift enum 类型遵循它。遵循此协议的枚举代表了一组有限的、预定义的选项。
  • 目的与作用
    1. 提供固定选项:当你的某个应用操作(App Intent)需要用户从一组固定选项中选择时,比如设置状态、选择类别、设定优先级等,AppEnum 就非常适用。
    2. 系统集成与用户体验
      • 快捷指令 (Shortcuts):系统可以根据 AppEnum 的定义,在快捷指令编辑器中为对应的参数自动生成一个选择器 (Picker),用户可以直观地看到所有可选项并进行选择。
      • Siri:Siri 可以更好地理解与这些枚举值相关的自然语言。例如,如果你的 AppEnum 有一个名为 “家庭” (Home) 的 Case,并且其显示名称也是 “家庭”,用户可以说 “将我的模式设置为家庭”,Siri 就能将其映射到对应的枚举值。
      • 聚焦搜索 (Spotlight) 和其他系统服务也可能利用这些信息。
    3. 类型安全:在代码中,你可以使用强类型的枚举,而不是依赖字符串或整数来表示选项,提高了代码的可读性和健壮性。
    4. 本地化AppEnum 的显示名称和各个 Case 的显示名称都支持本地化。

二、使用方式 (如何定义和使用) #

定义一个 AppEnum 通常涉及以下步骤:

  1. 创建 Swift 枚举:

    首先,定义一个标准的 Swift enum。为了方便使用和与系统交互,这个枚举通常建议:

    • 有一个原始值类型 (Raw Value),比如 StringInt (虽然技术上 AppEnum 不强制要求原始值,但有原始值通常是好做法,尤其对于持久化或跨平台)。
    • 遵循 CaseIterable 协议,这样系统可以方便地获取所有 Cases。
    • 遵循 Sendable 以确保线程安全 (在现代并发代码中推荐)。
    • 遵循 Hashable 通常也是有益的。
  2. 遵循 AppEnum 协议:

    让你的枚举遵循 AppEnum 协议。

  3. 实现必要的静态属性

    • static var typeDisplayRepresentation: TypeDisplayRepresentation:
      • 用于向用户描述这个枚举类型本身是什么。例如,如果你的枚举是 TaskStatus,那么这个表示可以是 “任务状态”。
      • TypeDisplayRepresentation 可以包含一个名称 (name)。
    • static var caseDisplayRepresentations: [Self: DisplayRepresentation]:
      • 这是一个字典,将枚举的每个 case 映射到一个 DisplayRepresentation 对象。
      • DisplayRepresentation 用于定义每个选项在用户界面中如何显示,可以包含标题 (title)、副标题 (subtitle) 和图像 (image)。标题是必需的。

示例代码:

假设我们要创建一个用于设置任务优先级的 AppEnum

import AppIntents
import SwiftUI // 只是为了 DisplayRepresentation 中的 Image 示例

// 1. 定义 Swift 枚举
enum TaskPriority: String, CaseIterable, Sendable, AppEnum { // 遵循 AppEnum
    case low, medium, high

    // 2. 实现 typeDisplayRepresentation
    static var typeDisplayRepresentation: TypeDisplayRepresentation {
        TypeDisplayRepresentation(name: "Task Priority") // 描述这个枚举类型是什么
    }

    // 3. 实现 caseDisplayRepresentations
    static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] {
        [
            .low: DisplayRepresentation(
                title: "Low", // "低" (如果本地化)
                subtitle: "Not very urgent", // 可选
                image: DisplayRepresentation.Image(systemName: "arrow.down.circle") // 可选
            ),
            .medium: DisplayRepresentation(
                title: "Medium", // "中"
                subtitle: "Moderately urgent",
                image: DisplayRepresentation.Image(systemName: "equal.circle")
            ),
            .high: DisplayRepresentation(
                title: "High", // "高"
                subtitle: "Very urgent",
                image: DisplayRepresentation.Image(systemName: "arrow.up.circle.fill")
            )
        ]
    }
}

// --- 如何在 AppIntent 中使用 AppEnum ---
struct SetTaskPriorityIntent: AppIntent {
    static var title: LocalizedStringResource = "Set Task Priority"
    static var description = IntentDescription("Sets the priority for a given task.")
    static var openAppWhenRun: Bool = false // 通常这类操作不需要打开 App

    // 使用 @Parameter 将 AppEnum 作为参数
    @Parameter(title: "Priority", description: "The priority level for the task.")
    var priority: TaskPriority // 参数类型是我们的 AppEnum

    // 假设我们还有一个任务ID参数
    @Parameter(title: "Task ID")
    var taskID: String? // 可选的任务ID

    @MainActor // 如果需要主线程操作
    func perform() async throws -> some IntentResult {
        guard let taskID = taskID else {
            // 可以通过 IntentResult 返回错误或请求用户提供 Task ID
            // 对于这个例子,我们简单打印
            print("Task ID is missing.")
            // iOS 17+
            // throw $taskID.needsValueError("Which task do you want to set priority for?")
            // 或者更早版本,通过其他方式处理参数缺失
            throw NSError(domain: "SetTaskPriorityIntent", code: 1, userInfo: [NSLocalizedDescriptionKey: "Task ID not provided."])
        }

        // 在这里,你可以使用 self.priority (例如 TaskPriority.high)
        // 来执行你的业务逻辑,比如更新数据库中的任务优先级
        print("Setting priority for task \(taskID) to: \(self.priority.rawValue)") // rawValue 是 "high", "medium", "low"

        // 模拟保存操作
        // DataStore.shared.updatePriority(for: taskID, to: self.priority)

        // 返回成功的结果,可以包含对话让 Siri 播报
        return .result(dialog: "Okay, task \(taskID) priority set to \(self.priority.displayRepresentation.title).")
    }
}

在上述 SetTaskPriorityIntent 中:

  • priority 参数的类型是 TaskPriority
  • 当用户在快捷指令 App 中配置这个 “Set Task Priority” 操作时,“Priority” 参数旁边会出现一个选择器,列出 “Low”、“Medium”、“High” 三个选项(以及它们对应的副标题和图像,如果提供了)。
  • 当用户通过 Siri 说:“嘿 Siri,用我的应用将任务 [任务名称或ID] 的优先级设为高”,Siri 能够理解 “高” 并将其匹配到 TaskPriority.high

三、使用场景 #

AppEnum 非常适合用于以下场景:

  1. 状态选择

    • 例如,一个聊天应用的意图 “设置我的状态”,状态可以是 OnlineOfflineBusyAway
    • 一个设备的意图 “设置设备模式”,模式可以是 SilentVibrateOnlyNormal
  2. 类别或类型选择

    • 一个笔记应用的意图 “创建笔记”,笔记类别可以是 WorkPersonalShopping
    • 一个锻炼应用的意图 “开始锻炼”,锻炼类型可以是 RunningCyclingSwimming (如果类型是固定的且数量不多)。
  3. 优先级或级别设定

    • 任务管理应用的意图 “设置任务优先级”,优先级如上例所示:LowMediumHigh
    • 游戏应用的意图 “设置难度”,难度可以是 EasyNormalHard
  4. 简单的配置选项

    • 一个主题切换意图 “设置应用主题”,主题可以是 LightDarkSystemDefault
    • 一个通知设置意图 “设置通知频率”,频率可以是 ImmediatelyHourlyDaily

关键考虑因素:

  • 选项的固定性AppEnum 最适合选项集合是相对固定且数量有限的情况。因为这些选项是在编译时定义的。
  • 动态选项:如果你的选项是动态生成的(例如,从服务器获取的列表,或者用户自己创建的标签列表),那么 AppEnum 不是最佳选择。在这种情况下,你应该考虑使用:
    • DynamicOptionsProvider 协议(用于 @Parameter):允许你在运行时动态提供选项列表。
    • 或者将选项建模为 AppEntity 并使用 EntityQuery 来提供可选择的实体列表。
  • 本地化TypeDisplayRepresentationnameDisplayRepresentationtitlesubtitle 都应该是 LocalizedStringResource 或支持本地化的字符串,以便在不同语言环境下正确显示。

总而言之,AppEnum 是 App Intents 框架中一个非常实用的工具,它通过结构化、类型安全且用户友好的方式,让你应用的操作参数能够提供一组清晰、固定的选择,从而提升了与 Siri、快捷指令等系统服务的集成体验。

本文共 2672 字,创建于 May 22, 2025
相关标签: Xcode, SwiftUI, ByAI, Gemini