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

AppEntity 是 App Intents 框架中的一个协议,用来让系统(如 Siri、快捷指令、Spotlight 等)识别你 App 中的某一类对象,比如:

  • 任务(Task)

  • 待办事项(ToDo)

  • 计时器(Timer)

  • 音乐、相册、联系人……

  • 你 App 中任何可操作的“实体”

换句话说,它是让你的 App 中的「对象」能成为 Siri / 快捷指令的输入和输出单位的桥梁。


🧱 AppEntity 的结构 #

struct Task: AppEntity {
    var id: String
    var title: String

    static var typeDisplayRepresentation: TypeDisplayRepresentation {
        "Task"
    }

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: title)
    }

    static var defaultQuery = TaskQuery()
}

必须实现的核心部分:

部分说明
id实体的唯一标识
typeDisplayRepresentationSiri 显示这个类型用什么名字(比如“任务”、“计时器”)
displayRepresentation显示具体某个实例时的名称(比如“写日报”)
defaultQuery提供搜索、选择功能的数据源入口(一般是一个查询器)

🧪 示例:你的计时器项目中加入 AppEntity #

Step 1:定义 Timer 实体 #

import AppIntents

struct FocusTimer: AppEntity, Identifiable {
    var id: String
    var name: String
    var duration: Int

    static var typeDisplayRepresentation: TypeDisplayRepresentation {
        "Focus Timer"
    }

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

    static var defaultQuery = FocusTimerQuery()
}

Step 2:定义实体查询器 #

你必须提供一个 EntityQuery 来告诉系统有哪些可选对象可以让用户挑选。

struct FocusTimerQuery: EntityQuery {
    func entities(for identifiers: [FocusTimer.ID]) async throws -> [FocusTimer] {
        // 通过 id 查找实体(通常来自你的数据库)
        sampleTimers.filter { identifiers.contains($0.id) }
    }

    func suggestedEntities() async throws -> [FocusTimer] {
        // 默认展示的候选项
        sampleTimers
    }

    var sampleTimers: [FocusTimer] {
        [
            FocusTimer(id: "1", name: "写作", duration: 25),
            FocusTimer(id: "2", name: "读书", duration: 15),
            FocusTimer(id: "3", name: "思考", duration: 30)
        ]
    }
}

Step 3:让 Intent 使用这个实体 #

struct StartNamedTimerIntent: AppIntent {
    static var title: LocalizedStringResource = "Start Named Timer"

    @Parameter(title: "Focus Session")
    var timer: FocusTimer

    func perform() async throws -> some IntentResult {
        TimerController.shared.startTimer(for: timer.duration)
        return .result(dialog: "Started \(timer.name) for \(timer.duration) minutes.")
    }
}

系统会自动提供一个“选择计时器”的 UI,让用户选择 写作 / 读书 等任务。


✅ 你为什么需要用 AppEntity? #

你希望支持的功能是否需要 AppEntity?
Siri 语音选择任务对象
快捷指令中提供列表选择
在 Widget 中显示任务列表
AppIntent 中传入自定义数据类型
简单数字 / 字符串参数

🧩 总结 #

名称用途
AppIntent一个操作(如“开始计时器”)
@ParameterAppIntent 的输入参数
AppEntityApp 中的数据对象(如任务、计时器、事项)
EntityQuery提供 AppEntity 的搜索、列表数据
AppShortcutsProvider系统级快捷方式展示支持

想让我帮你在当前的 Live Activity 项目中加入 AppEntity 版本的计时器选择吗?可以让用户对 Siri 说 “开始写作计时器”,然后自动开始 Live Activity。

很好!下面我将帮助你在当前的计时器 + Live Activity 项目中,加入基于 AppEntity 的计时器选择功能。最终你可以实现:

用户对 Siri 说:“开始写作计时器”,然后 App 通过 AppEntity 识别 “写作”,并自动启动对应的 Live Activity。


✅ 功能目标回顾 #

  • 定义多个预设计时器:写作 / 阅读 / 思考等

  • 使用 FocusTimer 作为 AppEntity

  • 用户在快捷指令 / Siri / Widget 中可以选择这些计时器

  • 启动后在灵动岛中展示对应名称和时间


🧱 项目结构更新 #

我们将新增三个文件:

StartNamedTimerIntent.swift      ← Intent,带实体参数
FocusTimer.swift                 ← AppEntity
FocusTimerQuery.swift           ← 查询器(数据源)

🧩 步骤一:定义 AppEntity(FocusTimer.swift) #

import AppIntents

struct FocusTimer: AppEntity, Identifiable {
    var id: String
    var name: String
    var duration: Int

    static var typeDisplayRepresentation: TypeDisplayRepresentation {
        "Focus Timer"
    }

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

    static var defaultQuery = FocusTimerQuery()
}

🧩 步骤二:定义实体查询器(FocusTimerQuery.swift) #

import AppIntents

struct FocusTimerQuery: EntityQuery {
    func entities(for identifiers: [FocusTimer.ID]) async throws -> [FocusTimer] {
        allTimers.filter { identifiers.contains($0.id) }
    }

    func suggestedEntities() async throws -> [FocusTimer] {
        allTimers
    }

    var allTimers: [FocusTimer] {
        [
            FocusTimer(id: "1", name: "写作", duration: 25),
            FocusTimer(id: "2", name: "阅读", duration: 15),
            FocusTimer(id: "3", name: "冥想", duration: 10)
        ]
    }
}

🧩 步骤三:创建 AppIntent(StartNamedTimerIntent.swift) #

import AppIntents
import ActivityKit

struct StartNamedTimerIntent: AppIntent {
    static var title: LocalizedStringResource = "Start Named Focus Timer"
    static var description = IntentDescription("Start a specific focus timer by name and show it in the Dynamic Island.")

    @Parameter(title: "Which Focus Timer")
    var timer: FocusTimer

    func perform() async throws -> some IntentResult {
        let duration = TimeInterval(timer.duration * 60)
        let attributes = TimerAttributes(taskName: timer.name)
        let contentState = TimerAttributes.ContentState(remainingTime: duration)

        do {
            _ = try Activity<TimerAttributes>.request(attributes: attributes, contentState: contentState)
            return .result(dialog: "已启动「\(timer.name)」计时器,共 \(timer.duration) 分钟")
        } catch {
            return .result(dialog: "启动失败,请稍后重试")
        }
    }
}

🧭 Siri / 快捷方式体验效果 #

  1. 打开快捷指令 App

  2. 添加操作 → 搜索你的 App → “Start Named Focus Timer”

  3. 你将看到一个计时器选择列表(写作、阅读、冥想)

  4. 运行后启动对应 Live Activity,并显示在灵动岛

  5. Siri 语音:说“开始写作计时器”也会识别到!


✅ 总结 #

模块用途
FocusTimer定义一个具体的计时器(AppEntity)
FocusTimerQuery系统调用时如何获取可选对象
StartNamedTimerIntent使用实体启动计时器并触发 Live Activity

是否需要我帮你加上:

  • 支持“结束计时器”的 AppIntent?

  • 使用 @IntentDialog 提供更自然的 Siri 对话语句?

  • 动态 Live Activity 自动更新倒计时进度?

这些都可以继续扩展这个项目。告诉我你的优先级即可。

非常好!我们将继续扩展你的 计时器 + AppEntity + Live Activity 项目,加入以下高级功能:


🎯 目标 #

  1. ✅ 当前已有:支持用 AppEntity 启动指定计时器(写作 / 阅读 / 冥想…)

  2. 🆕 本次新增:

    • ✅ 支持自动更新 Live Activity 倒计时(自动递减)

    • ✅ 添加一个 “结束计时器” 的 AppIntent

    • ✅ 使用 @IntentDialog 让 Siri 对话更自然


🧩 一、Live Activity 倒计时自动更新 #

我们需要持续更新 Activity 的 remainingTime,你可以使用定时器或后台任务管理。

修改 TimerAttributes.ContentState #

你已有:

struct ContentState: Codable, Hashable {
    var remainingTime: TimeInterval
}

添加一个后台控制器类:TimerController.swift #

import ActivityKit

class TimerController: ObservableObject {
    static let shared = TimerController()

    private var activity: Activity<TimerAttributes>?
    private var timer: Timer?

    func startActivity(taskName: String, duration: TimeInterval) {
        let attr = TimerAttributes(taskName: taskName)
        let state = TimerAttributes.ContentState(remainingTime: duration)

        do {
            activity = try Activity<TimerAttributes>.request(attributes: attr, contentState: state)
            startCountdown(duration: duration)
        } catch {
            print("启动 Live Activity 失败:\(error)")
        }
    }

    func startCountdown(duration: TimeInterval) {
        var remaining = duration
        timer?.invalidate()

        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            remaining -= 1
            Task {
                await self.activity?.update(using: .init(remainingTime: remaining))
            }
            if remaining <= 0 {
                self.endActivity()
            }
        }
    }

    func endActivity() {
        timer?.invalidate()
        Task {
            await activity?.end(using: .init(remainingTime: 0), dismissalPolicy: .immediate)
        }
    }
}

🧩 二、添加结束计时器的 AppIntent #

新建文件:StopTimerIntent.swift

import AppIntents

struct StopTimerIntent: AppIntent {
    static var title: LocalizedStringResource = "Stop Timer"

    static var description = IntentDescription("Ends the current focus timer and removes the Live Activity.")

    @IntentDialog("你想停止正在进行的计时器吗?")
    static var dialog: IntentDialog

    func perform() async throws -> some IntentResult {
        TimerController.shared.endActivity()
        return .result(dialog: "已结束计时器")
    }
}

🧩 三、升级 AppIntent 的 Siri 语义(对话自然化) #

更新 StartNamedTimerIntent.swift

@IntentDialog("开始「\(.parameter(\.timer))」计时器,对吗?")
static var dialog: IntentDialog

让 Siri 对话效果更自然,例如:

Siri: “开始「写作(25分钟)」计时器,对吗?”

你:“是的”

Siri: “已启动”


✅ 项目更新清单 #

文件说明
TimerController.swift控制 Live Activity 自动更新
StopTimerIntent.swift新增:支持结束计时器
StartNamedTimerIntent.swift更新:加上 Siri 对话优化
Live Activity Widget(已完成)

🔚 总结体验 #

你现在支持:

✅ 用 Siri/快捷指令 选择计时器并启动
✅ 自动显示灵动岛 Live Activity
✅ 实时倒计时进度更新
✅ 语音对话自然(AppIntent + IntentDialog)
✅ 支持结束当前计时器


是否需要我:

  • 加上任务记录(把每个完成的计时器存入 Core Data / UserDefaults)?

  • 做一个交互式 Widget,能点一下就启动一个指定计时器?

  • 加入多语言支持(中英文 AppIntent title / description)?

告诉我你想继续哪个方向,我可以继续帮你构建。

很好!我们现在将为你的计时器 + Live Activity 项目继续添加以下功能:


✅ 新增目标 #

  1. 任务记录:每次完成的计时器都自动记录(使用 UserDefaults 实现基础版)

  2. 交互式 Widget:支持从主屏幕 Widget 中点选某个计时器,直接启动 Live Activity


🧩 一、任务记录功能(UserDefaults 简易版) #

我们将新增一个记录完成任务的小结构和存储逻辑。

1. 定义模型 CompletedTimer.swift #

struct CompletedTimer: Identifiable, Codable {
    let id = UUID()
    let name: String
    let duration: Int
    let completedAt: Date
}

2. 创建一个管理器 TimerHistoryStore.swift #

class TimerHistoryStore: ObservableObject {
    static let shared = TimerHistoryStore()

    @Published private(set) var history: [CompletedTimer] = []

    private let key = "completedTimers"

    init() {
        load()
    }

    func addRecord(name: String, duration: Int) {
        let record = CompletedTimer(name: name, duration: duration, completedAt: .now)
        history.insert(record, at: 0)
        save()
    }

    private func load() {
        if let data = UserDefaults.standard.data(forKey: key),
           let decoded = try? JSONDecoder().decode([CompletedTimer].self, from: data) {
            self.history = decoded
        }
    }

    private func save() {
        if let data = try? JSONEncoder().encode(history) {
            UserDefaults.standard.set(data, forKey: key)
        }
    }
}

3. 在 TimerController 中结束时记录 #

func endActivity() {
    timer?.invalidate()
    Task {
        await activity?.end(using: .init(remainingTime: 0), dismissalPolicy: .immediate)
        if let attributes = activity?.attributes {
            TimerHistoryStore.shared.addRecord(name: attributes.taskName, duration: Int(attributes.durationMinutes))
        }
    }
}

你可以在 SwiftUI 页面中直接使用 @ObservedObject var store = TimerHistoryStore.shared 来展示记录。


🧩 二、交互式 Widget:启动指定计时器 #

WidgetKit + AppIntents 支持 点击 Widget → 执行 Intent

1. 定义 Widget 配置:TimerLauncherIntent.swift #

import AppIntents

struct TimerLauncherIntent: AppIntent {
    static var title: LocalizedStringResource = "Start Timer from Widget"

    @Parameter(title: "Select a Timer")
    var timer: FocusTimer

    func perform() async throws -> some IntentResult {
        TimerController.shared.startActivity(taskName: timer.name, duration: TimeInterval(timer.duration * 60))
        return .result()
    }
}

2. 创建 Widget #

struct TimerLauncherWidget: Widget {
    var body: some WidgetConfiguration {
        AppIntentConfiguration(kind: "widget.timer.launcher", intent: TimerLauncherIntent.self, provider: Provider()) { entry in
            Text(entry.timer.name)
        }
        .configurationDisplayName("快速启动计时器")
        .description("从主屏幕快速启动写作、阅读等计时器。")
        .supportedFamilies([.systemSmall])
    }
}

你可以让这个 Widget 通过 FocusTimerQuery 提供写作 / 阅读 / 冥想等选项,点击即启动。


✅ 小结 #

功能技术实现状态
🧾 记录完成的计时任务UserDefaults + Codable + ObservableObject✅ 已完成
⏱ 从 Widget 启动指定计时器AppIntentConfiguration + FocusTimer✅ 已完成

是否需要我帮你:

  • 将任务记录页面做成 SwiftUI 列表(展示过去完成记录)?

  • 将记录同步到 iCloud?

  • 让 Widget 动态展示下一个推荐任务?

告诉我你下一步的方向,我继续为你构建。

很好!我们现在继续为你的计时器项目添加以下功能:


✅ 新增功能目标 #

  1. 任务记录页面(SwiftUI 列表):展示用户完成的所有计时任务

  2. iCloud 同步任务记录:通过 @AppStorage + NSUbiquitousKeyValueStore 同步记录(基础实现)


🧩 一、任务记录页面(SwiftUI 列表) #

我们使用 TimerHistoryStore.shared 来显示记录内容。

TimerHistoryView.swift #

import SwiftUI

struct TimerHistoryView: View {
    @ObservedObject var store = TimerHistoryStore.shared

    var body: some View {
        NavigationView {
            List(store.history) { record in
                VStack(alignment: .leading, spacing: 4) {
                    Text(record.name)
                        .font(.headline)
                    Text("时长:\(record.duration) 分钟")
                        .font(.subheadline)
                    Text(record.completedAt.formatted(date: .abbreviated, time: .shortened))
                        .font(.caption)
                        .foregroundColor(.gray)
                }
                .padding(.vertical, 4)
            }
            .navigationTitle("任务记录")
        }
    }
}

你可以将这个页面作为 TabView 或设置入口显示:

TimerHistoryView()
    .tabItem {
        Label("记录", systemImage: "list.bullet.rectangle")
    }

🧩 二、记录 iCloud 同步功能(基础实现) #

修改 TimerHistoryStore 实现 #

我们将从 UserDefaults 切换为 NSUbiquitousKeyValueStore,以启用 iCloud 同步。

替换保存与读取逻辑 #

import Foundation
import Combine

class TimerHistoryStore: ObservableObject {
    static let shared = TimerHistoryStore()
    private let key = "completedTimers"
    private let store = NSUbiquitousKeyValueStore.default

    @Published private(set) var history: [CompletedTimer] = []

    init() {
        load()
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(iCloudDidChange(_:)),
            name: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
            object: store
        )
        store.synchronize()
    }

    func addRecord(name: String, duration: Int) {
        let record = CompletedTimer(name: name, duration: duration, completedAt: .now)
        history.insert(record, at: 0)
        save()
    }

    @objc private func iCloudDidChange(_ notification: Notification) {
        load()
    }

    private func load() {
        if let data = store.data(forKey: key),
           let decoded = try? JSONDecoder().decode([CompletedTimer].self, from: data) {
            DispatchQueue.main.async {
                self.history = decoded
            }
        }
    }

    private func save() {
        if let data = try? JSONEncoder().encode(history) {
            store.set(data, forKey: key)
            store.synchronize()
        }
    }
}

开启 iCloud 权限 #

确保在项目中:

  1. 打开 Xcode → 项目设置 → “Signing & Capabilities”

  2. 添加 iCloud 功能,勾选 Key-Value storage


✅ 总结体验 #

功能技术状态
✅ SwiftUI 任务记录列表List + ObservableObject完成
✅ iCloud 同步记录NSUbiquitousKeyValueStore完成

是否还需要:

  • 把任务记录导出为 CSV / 分享?

  • 更高级的同步方式(使用 CloudKit + Core Data)?

  • 添加搜索和筛选功能(比如只看“写作”记录)?

我可以继续帮你扩展这个系统,打造一款真正实用的极简专注工具。

非常好!我们继续为你的计时器项目增加以下功能:


✅ 新增功能目标 #

📤 导出任务记录为 CSV 文件,并支持系统分享(通过分享面板发送到 AirDrop、备忘录、邮箱等)


🧩 一、生成 CSV 数据 #

TimerHistoryStore.swift 中添加导出方法: #

extension TimerHistoryStore {
    func exportCSV() -> String {
        var csv = "名称,时长(分钟),完成时间\n"
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd HH:mm"

        for record in history {
            let line = "\"\(record.name)\",\(record.duration),\"\(formatter.string(from: record.completedAt))\""
            csv.append(line + "\n")
        }
        return csv
    }
}

添加导出按钮视图 #

import SwiftUI
import UniformTypeIdentifiers

struct TimerExportView: View {
    @State private var csvData: Data?
    @State private var isExporting = false

    var body: some View {
        VStack(spacing: 20) {
            Text("导出计时任务记录")
                .font(.title2)
                .bold()

            Button("生成并分享 CSV") {
                let csvString = TimerHistoryStore.shared.exportCSV()
                self.csvData = csvString.data(using: .utf8)
                self.isExporting = true
            }

            if let csvData = csvData {
                ShareLink(
                    item: csvData,
                    preview: SharePreview("任务记录.csv", image: Image(systemName: "square.and.arrow.up"))
                ) {
                    Label("通过分享发送", systemImage: "square.and.arrow.up")
                }
                .fileExporter(
                    isPresented: $isExporting,
                    document: CSVDocument(data: csvData),
                    contentType: .commaSeparatedText,
                    defaultFilename: "任务记录"
                ) { result in
                    switch result {
                    case .success:
                        print("导出成功")
                    case .failure(let error):
                        print("导出失败:\(error)")
                    }
                }
            }
        }
        .padding()
    }
}

🧩 三、支持导出为文档类型 #

创建 CSVDocument.swift(仅用于 fileExporter#

import SwiftUI
import UniformTypeIdentifiers

struct CSVDocument: FileDocument {
    static var readableContentTypes: [UTType] { [.commaSeparatedText] }

    var data: Data

    init(data: Data) {
        self.data = data
    }

    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents else {
            throw CocoaError(.fileReadCorruptFile)
        }
        self.data = data
    }

    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        return .init(regularFileWithContents: data)
    }
}

🧪 示例整合 #

你可以将 TimerExportView() 加入设置页面或独立 Tab 页面:

TimerExportView()
    .tabItem {
        Label("导出", systemImage: "square.and.arrow.up")
    }

✅ 总结 #

功能实现方式状态
导出记录为 CSVexportCSV() + DateFormatter✅ 已完成
分享面板ShareLink + fileExporter✅ 已完成
文件类型支持自定义 CSVDocument✅ 已完成
本文共 4567 字,创建于 May 19, 2025
相关标签: Xcode, SwiftUI, ByAI, ChatGPT