--- url: /doc_v2/zh/guide/AppIntent.md --- # AppIntent `AppIntentManager` 用于在 **Scripting** 中注册并管理 `AppIntent`,它是 Widget、Live Activity、ControlWidget 等控件执行脚本逻辑的核心机制。 所有的 `AppIntent` **必须** 定义在 `app_intents.tsx` 文件中,且在执行时其运行环境 `Script.env` 为 `"app_intents"`。 通过 `AppIntentManager` 注册的意图可以被 Widget / Live Activity / ControlWidget 中的 **Button** 与 **Toggle** 控件调用,以在用户交互时触发对应的脚本逻辑。 *** ## 一、类型定义 ### `AppIntent` 表示一个具体的应用意图实例。 | 字段名 | 类型 | 描述 | | ---------- | ------------------- | ---------------------------------------- | | `script` | `string` | 脚本路径,由系统内部生成。 | | `name` | `string` | 意图名称,唯一标识该 AppIntent。 | | `protocol` | `AppIntentProtocol` | 该意图实现的协议类型(如普通、音频播放、音频录制、Live Activity)。 | | `params` | `T` | 意图执行时的参数。 | *** ### `AppIntentFactory` 表示一个 **工厂函数**,用于通过参数创建 `AppIntent` 实例。 ```ts type AppIntentFactory = (params: T) => AppIntent ``` *** ### `AppIntentPerform` 表示一个执行函数,用于在意图被触发时执行实际逻辑。 ```ts type AppIntentPerform = (params: T) => Promise ``` *** ### `AppIntentProtocol` `AppIntentProtocol` 是枚举类型,用于指定意图的协议(行为类别)。 | 枚举成员 | 描述 | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `AppIntent` (0) | 普通 AppIntent。用于执行一般操作的意图。 | | `AudioPlaybackIntent` (1) | 播放、暂停或修改音频播放状态的意图。 | | `AudioRecordingIntent` (2) | (iOS 18.0+)启动、停止或修改音频录制状态的意图。**注意**:在 iOS/iPadOS 中,当使用 `AudioRecordingIntent` 协议时,必须在开始录音时启动一个 **Live Activity** 并在录音持续时保持活跃,否则音频录制将会停止。 | | `LiveActivityIntent` (3) | 启动、暂停或修改 Live Activity 的意图。 | *** ## 二、AppIntentManager 类 ### `AppIntentManager.register(options): AppIntentFactory` 注册一个新的 `AppIntent`。 通过指定 `name`、`protocol` 和 `perform` 函数来注册,当控件(Button/Toggle)被触发时,系统会自动调用 `perform` 函数执行逻辑。 ```ts static register(options: { name: string; protocol: AppIntentProtocol; perform: AppIntentPerform; }): AppIntentFactory ``` #### 参数: | 参数名 | 类型 | 描述 | | ---------- | --------------------- | ------------------------------------- | | `name` | `string` | AppIntent 的名称,需唯一,用于标识该意图。 | | `protocol` | `AppIntentProtocol` | AppIntent 的协议类型。 | | `perform` | `AppIntentPerform` | 当控件触发该意图时执行的异步函数,参数为控件传递过来的 `params`。 | #### 返回值: - **`AppIntentFactory`**:返回一个工厂函数,可通过传入参数创建 `AppIntent` 实例。 #### 示例: ```tsx /// app_intents.tsx export const ToggleDoorIntent = AppIntentManager.register({ name: "ToggleDoorIntent", protocol: AppIntentProtocol.AppIntent, perform: async ({ id, newState }: { id: string; newState: boolean }) => { // 自定义逻辑:切换门的状态 await setDoorState(id, newState) // 通知控件刷新状态(如 ControlWidgetToggle) ControlWidget.reloadToggles() } }) ``` 在控件文件中(如 `control_widget_toggle.tsx`): ```tsx ControlWidget.present( ) ``` 在小组件中使用(如 `widget.tsx`): ```tsx ``` *** ## 三、执行时环境 所有通过 `AppIntentManager` 定义的 AppIntent 在执行时,`Script.env` 会自动为 `"app_intents"`。 这意味着在 `perform` 函数中可以安全地使用适合 `"app_intents"` 环境的 API(如访问网络、更新 Live Activity 状态、触发控件刷新等)。 *** ## 四、最佳实践 1. **集中管理**:所有 AppIntent 必须定义在 `app_intents.tsx` 文件中,避免分散。 2. **类型安全**:在 `perform` 和控件参数中定义严格的参数类型 `T`,以确保开发时的自动补全与类型检查。 3. **协议匹配**:根据控件行为选择合适的 `AppIntentProtocol`,例如: - 普通操作 → `AppIntent` - 控制音频播放 → `AudioPlaybackIntent` - 控制音频录制 → `AudioRecordingIntent`(iOS 18+ 且需保持 Live Activity) - 启动/暂停 Live Activity → `LiveActivityIntent` 4. **状态刷新**:执行完 `perform` 后,如需更新 UI 状态(例如切换门锁开关),请调用 `ControlWidget.reloadButtons()` 、 `ControlWidget.reloadToggles()` 或 `Widget.reloadAll()`。 --- url: /doc_v2/zh/guide/Assistant/Assistant Conversation APIs.md --- # 智能助手会话 API Conversation API 用于**启动、控制和展示一个由系统托管的 Assistant 对话会话**。 该会话对应一个**完整的聊天页面(Chat Page)**,由 Scripting App 统一管理 UI、状态和模型交互。 与 `requestStreaming` / `requestStructuredData` 的区别在于: - Conversation API 面向**交互式聊天体验** - 系统负责消息发送、流式输出、Provider 切换、UI 渲染 - 开发者只需关注“何时开始 / 何时结束 / 是否展示” *** ## 会话生命周期概览 一个典型的会话生命周期如下: 1. `startConversation` —— 创建会话(可选自动开始) 2. `present` —— 展示 Assistant 聊天页面 3. 用户与 Assistant 进行交互 4. `dismiss` —— 临时关闭聊天页面(会话仍存在) 5. `present` —— 再次展示会话 6. `stopConversation` —— 结束会话并释放资源 重要约束: - **同一时间只能存在一个活动会话** - 若已有会话在运行,再次调用 `startConversation` 会抛出错误 - 调用 `stopConversation` 会自动触发 `dismiss` *** ## startConversation ### API 定义 ```ts function startConversation(options: { message: string images?: UIImage[] autoStart?: boolean systemPrompt?: string modelId?: string provider?: Provider }): Promise ``` *** ### 参数说明 #### options.message - 类型:`string` - 必填 - 会话的**初始用户消息** - 相当于聊天页面中的第一条用户输入 *** #### options.images(可选) - 类型:`UIImage[]` - 会与 `message` 一起作为首条用户消息发送 - 适用于: - 图片分析 - 拍照 / 截图后直接发起对话 *** #### options.autoStart(可选) - 类型:`boolean` - 默认值:`false` 行为说明: - `true`: - 创建会话后立即开始生成回复 - `false`: - 仅创建会话,不自动发送 - 通常配合 `present` 使用,由用户点击发送 *** #### options.systemPrompt(可选) - 类型:`string` 说明: - 若未提供: - 使用 Scripting Assistant 内置 system prompt - Assistant Tools 可用 - 若提供: - 完全替换默认 system prompt - **Assistant Tools 将不可用** 适用场景: - 构建高度定制的聊天角色 - 禁用工具调用,仅使用纯模型能力 *** #### options.modelId(可选) - 类型:`string` - 指定本次会话使用的模型 - 用户仍可在聊天页面中手动切换模型(若 UI 允许) *** #### options.provider(可选) - 类型:`Provider` - 指定默认 Provider - 用户可在聊天页面中更改 Provider(若 UI 允许) *** ### 返回值 ```ts Promise ``` - 会话创建成功即 resolve - 若已有会话存在,将 reject *** ## present ### API 定义 ```ts function present(): Promise ``` *** ### 行为说明 - 展示当前会话对应的 Assistant 聊天页面 - 若页面已展示,调用不会产生额外效果 - 可在以下场景调用: - `startConversation` 之后首次展示 - `dismiss` 后重新展示同一会话 *** ### 返回值 ```ts Promise ``` - 当聊天页面被用户关闭时 resolve *** ## dismiss ### API 定义 ```ts function dismiss(): Promise ``` *** ### 行为说明 - 关闭 Assistant 聊天页面 - **不会终止会话** - 会话状态、历史消息仍保留 适用场景: - 临时让出界面空间 - 页面跳转或多任务切换 *** ### 返回值 ```ts Promise ``` - 页面成功关闭后 resolve *** ## stopConversation ### API 定义 ```ts function stopConversation(): Promise ``` *** ### 行为说明 - 彻底终止当前会话 - 自动调用 `dismiss` - 清理会话状态与资源 - 结束后可再次调用 `startConversation` 创建新会话 *** ### 返回值 ```ts Promise ``` *** ## 会话状态相关常量 ### Assistant.isAvailable ```ts const isAvailable: boolean ``` - 表示当前用户是否**具备使用 Assistant 的权限** - 若为 `false`: - 所有 Conversation API 均不可用 *** ### Assistant.isPresented ```ts const isPresented: boolean ``` - 表示 Assistant 聊天页面当前是否处于展示状态 *** ### Assistant.hasActiveConversation ```ts const hasActiveConversation: boolean ``` - 表示当前是否存在一个活动会话 - 常用于防止重复调用 `startConversation` *** ## 使用示例 ### 示例一:最常见的使用方式 ```ts await Assistant.startConversation({ message: "帮我总结这篇文章的要点", autoStart: true }) await Assistant.present() ``` *** ### 示例二:创建会话但不自动发送 ```ts await Assistant.startConversation({ message: "我们来讨论一下系统架构设计", autoStart: false }) await Assistant.present() // 由用户在 UI 中手动点击发送 ``` *** ### 示例三:暂时关闭,再次展示 ```ts await Assistant.startConversation({ message: "分析这张图片", images: [image], autoStart: true }) await Assistant.present() // 用户关闭页面 await Assistant.dismiss() // 稍后再次展示同一会话 await Assistant.present() ``` *** ### 示例四:结束当前会话并重新开始 ```ts if (Assistant.hasActiveConversation) { await Assistant.stopConversation() } await Assistant.startConversation({ message: "开始一个新的话题", autoStart: true }) await Assistant.present() ``` *** ## 使用建议与最佳实践 - 将 Conversation API 视为“**托管聊天界面**” - 不要在同一业务流中混用 Conversation API 与 `requestStreaming` - 在调用 `startConversation` 前检查 `hasActiveConversation` - 若仅需要数据或一次性输出,应使用: - `requestStructuredData` - `requestStreaming` - 若用户需要持续交互体验,应使用 Conversation API *** ## 设计边界说明 - Conversation API 不适合无 UI 场景 - 不适合后台自动化任务 - 不适合需要完全控制 Prompt / Token / 输出格式的场景 --- url: /doc_v2/zh/guide/Assistant/Assistant Quick Start.md --- # 快速了解智能助手 Scripting 的 Assistant API 提供了三类能力,分别面向 **数据处理**、**流式输出** 和 **交互式聊天** 三种不同使用场景。 在使用之前,建议先明确你的需求属于哪一类。 *** ## Assistant API 分类总览 | API 分类 | 主要方法 | 适用场景 | | ------ | ---------------------------------------------------------------- | ---------------------- | | 结构化数据 | `requestStructuredData` | 从文本 / 图片中提取结构化 JSON 数据 | | 流式生成 | `requestStreaming` | 实时展示 AI 输出内容 | | 会话聊天 | `startConversation` / `present` / `dismiss` / `stopConversation` | 托管式聊天体验 | *** ## requestStructuredData **用途** 用于请求**严格符合 JSON Schema 的结构化结果**。 **适合场景** - 解析票据、发票、账单 - 从自然语言中提取字段 - 生成配置、规则、表单数据 - 需要直接用于程序逻辑的数据 **特点** - 返回值稳定、可预测 - 不适合长文本或展示型输出 - 适合后台或无 UI 场景 **一句话总结** > 需要“数据”,用 `requestStructuredData` *** ## requestStreaming **用途** 用于获取**流式输出**,在模型生成过程中持续接收内容。 **适合场景** - 聊天气泡逐字显示 - 长文本生成(文章、说明、分析) - 需要低延迟反馈的 UI **特点** - 支持文本、推理、用量等多种 Chunk - 可边生成边渲染 - 不保证输出结构 **一句话总结** > 需要“过程”和“实时展示”,用 `requestStreaming` *** ## Conversation API(会话聊天) **相关方法** - `startConversation` - `present` - `dismiss` - `stopConversation` **用途** 用于创建并展示一个**系统托管的 Assistant 聊天页面**。 **适合场景** - 类 ChatGPT 的交互体验 - 用户需要多轮对话 - 希望系统管理 UI、Provider 切换、消息状态 **特点** - 内置完整聊天 UI - 自动处理流式输出 - 同一时间仅支持一个会话 **一句话总结** > 需要“完整聊天体验”,用 Conversation API *** ## 如何选择合适的 API ### 常见选择指南 - **我要解析一张账单 →** `requestStructuredData` - **我要展示 AI 写文章的过程 →** `requestStreaming` - **我要打开一个聊天页面让用户和 AI 对话 →** Conversation API - **我不需要 UI,只要结果 →** `requestStructuredData` 或 `requestStreaming` - **我希望系统帮我处理聊天 UI →** Conversation API *** ## 简单示例 ### 结构化数据 ```ts const data = await Assistant.requestStructuredData(...) ``` *** ### 流式输出 ```ts const stream = await Assistant.requestStreaming(...) for await (const chunk of stream) { // handle chunk } ``` *** ### 聊天会话 ```ts await Assistant.startConversation({ message: "你好", autoStart: true }) await Assistant.present() ``` *** ## 使用建议 - 不要在同一业务流程中混用 Conversation API 和 `requestStreaming` - 有明确数据结构需求时,优先选择 `requestStructuredData` - 展示型输出和交互体验优先考虑 `requestStreaming` 或 Conversation API *** ## 下一步 如果你需要更深入的内容,可以继续阅读: - `requestStructuredData` 详细文档 - `requestStreaming` 详细文档 - Conversation API 生命周期说明 --- url: /doc_v2/zh/guide/Assistant/requestStreaming.md --- # 请求流式数据 `requestStreaming` 用于向 Assistant 请求**流式输出(Streaming Response)**。 与一次性返回完整结果不同,该 API 会在模型生成内容的过程中**持续返回数据片段(Chunk)**,调用方可以边接收边处理,从而实现: - 实时展示 AI 输出(打字机效果) - 流式日志 / 分段结果处理 - 长文本生成的低延迟体验 - 在生成过程中提前终止或切换 UI 状态 该 API 返回的是一个 **`ReadableStream`**,你可以通过 `for await ... of` 逐块读取。 *** ## API 定义 ```ts function requestStreaming(options: { systemPrompt?: string | null messages: MessageItem | MessageItem[] provider?: Provider modelId?: string }): Promise> ``` *** ## 参数说明 ### options.systemPrompt(可选) - 类型:`string | null` - 指定本次请求使用的 system prompt - 若未提供: - 使用 Assistant 内置的默认 system prompt - 若显式传入: - 将**完全替换默认 system prompt** - Assistant Tools 将**不可用** 适用场景: - 构建专用角色(如代码审查、翻译、摘要) - 严格约束模型行为或输出风格 *** ### options.messages - 类型:`MessageItem | MessageItem[]` - 必填 - 用于描述对话上下文的消息列表 #### MessageItem 结构 ```ts type MessageItem = { role: "user" | "assistant" content: MessageContent | MessageContent[] } ``` - `role` - `"user"`:用户输入 - `"assistant"`:历史 Assistant 输出(用于上下文补全) *** ### MessageContent 类型 #### 文本内容 ```ts type MessageTextContent = | string | { type: "text"; content: string } ``` *** #### 图片内容 ```ts type MessageImageContent = { type: "image" content: string // data:image/...;base64,... } ``` *** #### 文档内容 ```ts type MessageDocumentContent = { type: "document" content: { mediaType: string data: string // base64 } } ``` *** ### options.provider(可选) - 类型:`Provider` - 指定使用的 AI Provider - 若未指定: - 使用 Assistant 当前配置的默认 Provider - 支持: - `"openai"` - `"gemini"` - `"anthropic"` - `"deepseek"` - `"openrouter"` - `{ custom: string }` *** ### options.modelId(可选) - 类型:`string` - 指定具体模型 ID - 必须与 Provider 实际支持的模型匹配 - 若未指定,使用 Provider 默认模型 *** ## 返回值 ```ts Promise> ``` 该 Promise resolve 后,你将获得一个可异步迭代的流对象。 *** ## StreamChunk 类型说明 `requestStreaming` 的流中会返回以下三类 Chunk。 *** ### StreamTextChunk(文本输出) ```ts type StreamTextChunk = { type: "text" content: string } ``` - 表示 Assistant 生成的**可展示文本** - 多个 chunk 拼接后构成完整回复 *** ### StreamReasoningChunk(推理输出) ```ts type StreamReasoningChunk = { type: "reasoning" content: string } ``` - 表示模型的**中间推理过程** - 是否返回、返回粒度取决于 Provider / Model *** ### StreamUsageChunk(用量信息) ```ts type StreamUsageChunk = { type: "usage" content: { totalCost: number | null cacheReadTokens: number | null cacheWriteTokens: number | null inputTokens: number outputTokens: number } } ``` 说明: - 通常在流的**末尾**返回一次 - 不同 Provider 支持的字段略有差异 - `totalCost` 可能为 `null`(例如 Provider 未提供费用信息) *** ## 使用示例 ### 示例一:最基本的流式请求 ```ts const stream = await Assistant.requestStreaming({ messages: { role: "user", content: "给我讲一个简短的科幻故事" }, provider: "openai" }) let text = "" for await (const chunk of stream) { if (chunk.type === "text") { text += chunk.content console.log(chunk.content) } } ``` *** ### 示例二:区分文本、推理和用量 ```ts const stream = await Assistant.requestStreaming({ systemPrompt: "你是一个严谨的技术写作助手", messages: [ { role: "user", content: "解释什么是 HTTP/3" } ] }) let answer = "" let reasoningLog = null let usage = null for await (const chunk of stream) { switch (chunk.type) { case "text": answer += chunk.content break case "reasoning": reasoningLog = (reasoningLog ?? "") + chunk.content break case "usage": usage = chunk.content break } } console.log(answer) console.log(usage) ``` *** ### 示例三:包含图片与文档的流式请求 ```ts const stream = await Assistant.requestStreaming({ messages: [ { role: "user", content: [ { type: "text", content: "请分析这份文档的核心内容" }, { type: "document", content: { mediaType: "application/pdf", data: "JVBERi0xLjQKJcfs..." } } ] } ], provider: "anthropic" }) for await (const chunk of stream) { if (chunk.type === "text") { console.log(chunk.content) } } ``` *** ## 使用建议与注意事项 - 流式结果**必须按顺序消费**,不可并发读取 - UI 场景下建议: - 文本 chunk 实时渲染 - reasoning chunk 仅用于调试 - usage chunk 延迟处理 - 若中途不再需要结果,应主动中止读取,避免无意义消耗 - 并非所有 Provider / Model 都会返回 reasoning 或 usage - 不同 Provider 的 chunk 粒度不同,不应假设单次 chunk 是完整句子 --- url: /doc_v2/zh/guide/Assistant/requestStructuredData.md --- # 请求结构化数据 `requestStructuredData` 用于向 Assistant 请求**严格符合指定 JSON Schema 的结构化 JSON 数据**。 该 API 适合在你需要**可预测、可直接用于程序逻辑**的数据结果时使用,而不是自由文本。 典型使用场景包括: - 从自然语言中提取结构化字段 - 解析发票、收据、账单、票据 - 生成配置对象、规则数据 - 在不同 AI Provider / Model 之间获得一致的数据结构 *** ## 支持的 JSON Schema 类型 Scripting 提供了一套轻量级、跨模型可用的 Schema 描述方式,由三种基础类型组成。 ### Primitive(基础类型) ```ts type JSONSchemaPrimitive = { type: "string" | "number" | "boolean" required?: boolean description: string } ``` *** ### Object(对象类型) ```ts type JSONSchemaObject = { type: "object" properties: Record required?: boolean description: string } ``` *** ### Array(数组类型) ```ts type JSONSchemaArray = { type: "array" items: JSONSchemaType required?: boolean description: string } ``` *** ## API 定义 ### 不包含图片输入 ```ts function requestStructuredData( prompt: string, schema: JSONSchemaArray | JSONSchemaObject, options?: { provider: Provider modelId?: string } ): Promise ``` *** ### 包含图片输入 ```ts function requestStructuredData( prompt: string, images: string[], schema: JSONSchemaArray | JSONSchemaObject, options?: { provider: Provider modelId?: string } ): Promise ``` *** ## 参数说明 ### prompt - 类型:`string` - 必填 - 向模型说明**需要解析或生成什么结构化数据** - 强烈建议在 prompt 中明确: - 时间格式(如 ISO-8601) - 金额是否为纯数字 - 缺失字段的处理规则 *** ### images(可选) - 类型:`string[]` - 每一项必须是 Data URI,例如: ```text data:image/png;base64,iVBORw0KGgoAAAANS... ``` - 并非所有 Provider / Model 都支持图片输入 - 图片数量过多可能导致请求失败 *** ### schema - 类型:`JSONSchemaArray | JSONSchemaObject` - 必填 - 定义模型**唯一允许返回的 JSON 结构** - 每一个字段都应提供清晰的 `description`,这是保证结果稳定的关键 *** ### options.provider - 类型:`Provider` - 可选,未指定时使用 Assistant 当前默认 Provider - 支持: - `"openai"` - `"gemini"` - `"anthropic"` - `"deepseek"` - `"openrouter"` - `{ custom: string }` *** ### options.modelId(可选) - 类型:`string` - 指定具体模型 ID - 必须与 Provider 实际支持的模型匹配 - 未指定时使用 Provider 默认模型 *** ## 返回值 ```ts Promise ``` - `R` 为调用方声明的泛型类型 - 返回结果应严格符合 Schema 描述 - 若模型无法生成合法结构,Promise 将被 reject *** ## 使用示例 ### 示例一:解析票据 / 收据,提取消费项目、时间和金额 该示例演示如何将一段票据文本解析为结构化数据,包括: - 整体消费时间(`purchasedAt`) - 币种(`currency`) - 消费项目列表(`items`) - 项目名称 - 项目时间(若无则为空) - 金额 - 合计金额(`total`) ```ts type ReceiptItem = { name: string time: string amount: number } type ReceiptParsed = { purchasedAt: string currency: string items: ReceiptItem[] total: number } const receiptText = ` Star Coffee 2026-01-08 14:23 Latte (Large) $5.50 Blueberry Muffin $3.20 Tax $0.79 Total $9.49 ` const parsed = await Assistant.requestStructuredData( [ "请分析以下票据文本,并提取结构化信息:", "- purchasedAt:整体消费时间,使用 ISO-8601 格式,若无法判断则返回空字符串", "- currency:币种代码(如 USD / EUR / CNY),若无法判断则返回空字符串", "- items:仅包含实际消费项目,不包含税费、合计等行", " - name:项目名称", " - time:项目级时间,若无则返回空字符串", " - amount:数值类型的金额", "- total:合计金额,若无则返回 -1", "", "票据内容:", receiptText ].join("\n"), { type: "object", description: "票据解析结果", properties: { purchasedAt: { type: "string", description: "整体消费时间(ISO-8601),若无则为空字符串" }, currency: { type: "string", description: "币种代码,若无法判断则为空字符串" }, items: { type: "array", description: "消费项目列表(不包含税费、合计等)", items: { type: "object", description: "单个消费项目", properties: { name: { type: "string", description: "项目名称" }, time: { type: "string", description: "项目时间(ISO-8601),若无则为空字符串" }, amount: { type: "number", description: "项目金额" } } } }, total: { type: "number", description: "合计金额,若不存在则为 -1" } } }, { provider: "openai" } ) // 建议在业务层将空字符串 / -1 归一化为 null console.log(parsed) ``` *** ### 示例二:生成数组结构 ```ts type Expense = { name: string amount: number } const expenses = await Assistant.requestStructuredData( "列出三项常见的日常支出及其大致金额", { type: "array", description: "支出列表", items: { type: "object", description: "单项支出", properties: { name: { type: "string", description: "支出名称" }, amount: { type: "number", description: "金额" } } } }, { provider: "gemini" } ) ``` *** ### 示例三:结合图片生成结构化结果 ```ts type ImageSummary = { description: string containsText: boolean } const summary = await Assistant.requestStructuredData( "分析这张图片的主要内容", ["data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."], { type: "object", description: "图片分析结果", properties: { description: { type: "string", description: "图片内容描述" }, containsText: { type: "boolean", description: "是否包含可识别文本" } } }, { provider: "openai" } ) ``` *** ## 使用建议与注意事项 - 当返回结果用于业务逻辑时,优先使用 `requestStructuredData` - Schema 描述越明确,结果越稳定 - 复杂业务规则不要放在 Schema 中,应由业务代码处理 --- url: /doc_v2/zh/guide/AssistantTool.md --- # 智能助手工具 Assistant Tool 是 Scripting 应用中为智能助手(Assistant)提供系统功能扩展的机制。通过定义和实现 Assistant Tool,开发者可以为 Assistant 提供设备能力访问、文件读写操作、数据分析处理等辅助功能,提升 Assistant 的智能性和实用性。 本文以一个示例工具「Request Current Location」为基础,介绍 Assistant Tool 的完整实现流程,包括工具创建、配置文件说明、执行逻辑实现以及各类函数的详细说明。 *** ## 一、工具创建流程 1. 打开任意脚本项目,在文件管理界面点击“添加 Assistant Tool”按钮。 2. 在弹出的配置窗口中填写Assistant Tool 相关的信息。 3. 点击“保存”后,系统会自动在脚本中生成两个文件: - `assistant_tool.json`:描述工具的元数据和参数信息。 - `assistant_tool.tsx`:实现工具的执行逻辑。 *** ## 二、配置文件 assistant\_tool.json 该文件用于声明工具的基本信息和行为配置。以下是示例内容及字段说明: ```json { "displayName": "Request Current Location", "id": "request_current_location", "description": "This tool allows you to request the one-time delivery of the latitude and longitude of the user’s current location.", "icon": "location.fill", "color": "systemBlue", "parameters": [], "requireApproval": true, "autoApprove": true, "scriptEditorOnly": false } ``` ### 字段说明: | 字段 | 类型 | 说明 | | ------------------ | ------- | ------------------- | | `displayName` | string | 工具在界面中显示的名称 | | `id` | string | 工具唯一标识符,不能重复 | | `description` | string | 工具功能描述 | | `icon` | string | 使用的 SF Symbols 图标名 | | `color` | string | 工具主色调 | | `parameters` | array | 工具需要的参数(为空表示无输入) | | `requireApproval` | boolean | 是否需要用户批准 | | `autoApprove` | boolean | 是否支持 Assistant 自动批准 | | `scriptEditorOnly` | boolean | 工具是否仅能在脚本编辑器中使用 | *** ## 三、执行逻辑 assistant\_tool.tsx 实现示例 ```tsx type RequestCurrentLocationParams = {} const locationApprovalRequest: AssistantToolApprovalRequestFn = async ( params, ) => { return { message: "The assistant wants to request your current location.", primaryButtonLabel: "Allow" } } const requestCurrentLocation: AssistantToolExecuteWithApprovalFn = async ( params, { primaryConfirmed, secondaryConfirmed, } ) => { try { const location = await Location.requestCurrent() if (location) { return { success: true, message: [ "The user's current location info:", `${location.latitude}`, `${location.longitude}` ].join("\n") } } return { success: false, message: "Failed to request user's current location, ask user to check the device's location permission." } } catch { return { success: false, message: "Failed to request user's current location, ask user to check the device's location permission." } } } const testRequestLocationApprovalFn = AssistantTool.registerApprovalRequest( locationApprovalRequest ) const testRequestLocationExecuteFn = AssistantTool.registerExecuteToolWithApproval( requestCurrentLocation ) // 可在脚本编辑器中运行以下测试代码: testRequestLocationApprovalFn({}) testRequestLocationExecuteFn({}, { primaryConfirmed: true, secondaryConfirmed: false }) ``` *** ## 四、AssistantTool 注册函数详解 ### 1. `registerApprovalRequest` 注册一个函数,在执行工具前向用户请求批准。 ```ts function registerApprovalRequest

( requestFn: AssistantToolApprovalRequestFn

): AssistantToolApprovalRequestTestFn

``` **参数说明**: - `requestFn(params, scriptEditorProvider?)`:返回提示信息,包括 message、按钮文本等。 - `params`:工具执行时的输入参数。 - `scriptEditorProvider`:仅在工具设置为仅限脚本编辑器使用时可用,提供脚本文件访问能力。 **返回值说明**: 返回的测试函数可用于在脚本编辑器中模拟触发批准请求。 *** ### 2. `registerExecuteToolWithApproval` 注册一个需要用户批准的执行函数。 ```ts function registerExecuteToolWithApproval

( executeFn: AssistantToolExecuteWithApprovalFn

): AssistantToolExecuteWithApprovalTestFn

``` **参数说明**: - `params`:工具执行时的输入参数。 - `userAction`:用户在批准提示中选择的操作: ```ts type UserActionForApprovalRequest = { primaryConfirmed: boolean secondaryConfirmed: boolean } ``` - `scriptEditorProvider`:同上。 **返回值说明**: 返回一个对象: ```ts { success: boolean message: string } ``` - `success`: 是否执行成功。 - `message`: 返回给Assistant的执行成功或失败的信息。 *** ### 3. `registerExecuteTool` 注册一个不需要用户批准的工具逻辑。 ```ts function registerExecuteTool

( executeFn: AssistantToolExecuteFn

): AssistantToolExecuteTestFn

``` **适用场景**:如操作无敏感性、不涉及设备权限时,可使用此方式。 *** ### 4. 测试函数使用 每个注册函数会返回对应的测试函数,可在脚本中运行: ```ts testApprovalRequestFn({ ...params }) testExecuteFn({ ...params }, { primaryConfirmed: true, secondaryConfirmed: false, }) testExecuteToolFn({ ...params }) ``` *** ## 五、脚本编辑器接口说明(ScriptEditorProvider) 当工具设置为 `scriptEditorOnly: true` 时,系统提供 `ScriptEditorProvider` 接口,允许访问脚本项目的文件系统与语法信息。 接口能力包括: - 文件读写(读取、更新、写入、插入、替换) - 差异比较(openDiffEditor) - 语法检查结果(getLintErrors) - 获取项目中所有文件/文件夹列表 适用于如格式化脚本、批量修改内容等编辑类工具。 *** ## 六、执行与用户体验流程 1. Assistant 在会话中判断是否需要调用某个工具。 2. 如果工具设置为需要批准,系统弹出批准对话框: - 显示由 `registerApprovalRequest` 返回的提示信息。 - 用户点击“允许”后执行工具逻辑。 3. 执行结果通过 `message` 字段返回给 Assistant,并可呈现给用户。 *** ## 七、无需批准的工具实现方式 当不需要显示批准提示时,可直接使用 `registerExecuteTool` 注册逻辑函数: ```ts AssistantTool.registerExecuteTool(async (params) => { // 执行逻辑 return { success: true, message: "Tool executed successfully." } }) ``` 将 `assistant_tool.json` 中的 `requireApproval` 字段设置为 `false` 即可。 *** ## 八、小结 Assistant Tool 是 Scripting 应用提供的可扩展能力模块,支持用户授权、文件操作、系统调用等多种场景。开发流程主要包括: 1. 在脚本项目中创建工具; 2. 配置工具元信息; 3. 实现逻辑函数并注册; 4. 使用测试函数验证行为; 5. 在 Assistant 会话中自动或主动触发执行。 --- url: /doc_v2/zh/guide/Changelog/2.4.3/Animation and Transition.md --- # 动画和过渡 Scripting 通过 `Observable` / `useObservable`、`Animation`、`Transition`、`withAnimation` 以及视图的 `animation` / `transition` 属性,基本对齐了 SwiftUI 的动画能力,包括: - **属性动画**:数值、颜色、布局等属性随状态变化平滑过渡 - **过渡动画**:视图插入 / 移除时的进出效果(如淡入淡出、滑入滑出、翻转) - **显式动画**:通过 `withAnimation` 包裹一段「状态更新代码」统一加动画 ## Animation 类 `Animation` 用来描述「属性变化的时间曲线与节奏」,类似 SwiftUI 的 `Animation`。 ### 工厂方法(创建动画) #### `Animation.default()` ```ts static default(): Animation ``` - 创建一个默认动画(通常是系统预设的 ease-in-out 曲线) - 无需配置,适合「只想要一个普通的过渡效果」的场景 示例: ```tsx 默认动画 ``` *** #### `Animation.linear(duration?)` ```ts static linear(duration?: DurationInSeconds | null): Animation ``` - 匀速动画,整段时间内速度保持恒定 - `duration`:动画持续时间(秒),可选,不传时使用默认时长 适合:进度条数值增长、颜色线性变化等。 *** #### `Animation.easeIn(duration?)` ```ts static easeIn(duration?: DurationInSeconds | null): Animation ``` - 开始慢、后面加速 - 适合:元素「加速进入」的感觉 *** #### `Animation.easeOut(duration?)` ```ts static easeOut(duration?: DurationInSeconds | null): Animation ``` - 开始快、结尾慢 - 适合:元素「减速停止」的感觉,如卡片滑入后停在目标位置 *** #### `Animation.bouncy(options?)` ```ts static bouncy(options?: { duration?: DurationInSeconds extraBounce?: number }): Animation ``` - 带回弹效果的动画 - 参数: - `duration`:总时长(秒) - `extraBounce`:额外弹性,越大越明显 适合:按钮点击放大回弹、卡片弹出等「有趣」的动效。 *** #### `Animation.smooth(options?)` ```ts static smooth(options?: { duration?: DurationInSeconds extraBounce?: number }): Animation ``` - 相对柔和、过渡自然的动画 - 与 `bouncy` 相比,弹性感更弱,更偏「丝滑」 *** #### `Animation.snappy(options?)` ```ts static snappy(options?: { duration?: DurationInSeconds extraBounce?: number }): Animation ``` - 动作「干脆利落」,响应速度快 - 常见于触控反馈、选中高亮等瞬间反馈场景 *** #### `Animation.spring(options?)` ```ts static spring(options?: { blendDuration?: number } & ({ duration?: DurationInSeconds bounce?: number response?: never dampingFraction?: never } | { response?: number dampingFraction?: number duration?: never bounce?: never })): Animation ``` 支持两种配置方式(注意互斥): 1. **基于时长的弹簧动画** - `duration`: 动画持续时间 - `bounce`: 弹性大小 2. **物理参数模式** - `response`: 响应速度(值越小反馈越快) - `dampingFraction`: 阻尼系数(0\~1,越大越「稳」,越小越「弹」) 额外参数: - `blendDuration`:动画混合时长,用于多动画衔接场景(可选) 示例: ```tsx // 简单弹簧 const anim1 = Animation.spring({ duration: 0.4, bounce: 0.3 }) // 高级弹簧 const anim2 = Animation.spring({ response: 0.25, dampingFraction: 0.7 }) ``` *** #### `Animation.interactiveSpring(options?)` ```ts static interactiveSpring(options?: { response?: number dampingFraction?: number blendDuration?: number }): Animation ``` - 面向「交互驱动」的弹簧动画,例如拖拽结束后的回弹 - 参数与 `spring` 的物理参数模式类似,语义更偏向手势交互 *** #### 0 `Animation.interpolatingSpring(options?)` ```ts static interpolatingSpring(options?: { mass?: number stiffness: number damping: number initialVelocity?: number } | { duration?: DurationInSeconds bounce?: number initialVelocity?: number mass?: never stiffness?: never damping?: never }): Animation ``` 两种配置方式(互斥): 1. **物理参数模式** - `mass`: 质量 - `stiffness`: 刚度 - `damping`: 阻尼 - `initialVelocity`: 初速度(可选) 2. **时长 + 弹性模式** - `duration`: 动画时长 - `bounce`: 弹性 - `initialVelocity`: 初速度(可选) 适合对动态效果「非常在意手感」的高级场景。 *** ### 修改已有动画(链式 API) #### `delay(time)` ```ts delay(time: DurationInSeconds): Animation ``` - 使动画延迟 `time` 秒后再开始 - 返回一个新的 `Animation` 实例(原动画不变) 示例: ```tsx const [animValue, setAnimValue] = useState(0) const anim = Animation .spring({ duration: 0.4, bounce: 0.3 }) .delay(0.2) 延迟弹簧 ``` *** #### `repeatCount(count, autoreverses?)` ```ts repeatCount(count: number, autoreverses?: boolean): Animation ``` - 重复执行动画 `count` 次 - `autoreverses`(默认 `true`):是否来回反向播放 示例: ```tsx const pulse = Animation .easeIn(0.6) .repeatCount(3, true) 闪烁三次 ``` *** #### `repeatForever(autoreverses?)` ```ts repeatForever(autoreverses?: boolean): Animation ``` - 无限次重复动画 - 适合加载动画、呼吸灯效果等 *** ### Animation 实战示例 #### 示例 1:基本大小动画 ```tsx import { VStack, Button, Rectangle, useObservable, Animation, withAnimation } from "scripting" export function Demo() { const size = useObservable(80) return `) await webView.present({ navigationTitle: '网页视图示例' }) webView.dispose() ``` --- url: /doc_v2/zh/guide/Changelog/2.4.6/Device/index.md --- # 设备 `Device` 命名空间提供当前运行设备的基础信息、系统环境、屏幕与电池状态、语言与地区设置,以及部分与设备能力相关的方法(如唤醒锁、网络接口信息)。 该 API 常用于: - 设备差异化逻辑(iPhone / iPad / Mac) - UI 布局与适配 - 多语言与本地化判断 - 网络调试与诊断 - 防止脚本执行期间设备休眠 *** ## 设备与系统信息 ### `Device.model: string` 设备型号名称,例如 `"iPhone"`、`"iPad"`。 *** ### `Device.systemName: string` 当前操作系统名称,例如 `"iOS"`、`"iPadOS"`、`"macOS"`。 *** ### `Device.systemVersion: string` 当前操作系统版本号,例如 `"17.2"`。 *** ### `Device.isiPhone: boolean` 当前设备是否为 iPhone。 *** ### `Device.isiPad: boolean` 当前设备是否为 iPad。 *** ### `Device.isiOSAppOnMac: boolean` 当前进程是否为 **iPhone / iPad App 运行在 Mac 上**(Mac Catalyst / Apple Silicon Mac)。 *** ## 屏幕信息 ### `Device.screen` 当前主屏幕的信息。 ```ts { width: number height: number scale: number } ``` 字段说明: - `width`:屏幕逻辑宽度(pt) - `height`:屏幕逻辑高度(pt) - `scale`:屏幕缩放因子(如 2 / 3) 常用于布局计算、画布尺寸、截图或渲染比例控制。 *** ## 方向与姿态 ### `Device.isPortrait: boolean` 当前设备是否处于竖屏方向。 *** ### `Device.isLandscape: boolean` 当前设备是否处于横屏方向。 *** ### `Device.isFlat: boolean` 设备是否平放(例如放在桌面上)。 该值通常基于设备姿态传感器,用于高级交互或方向判断。 *** ## 外观与主题 ### `Device.colorScheme: ColorScheme` 当前系统外观模式。 可能的值通常包括: - `light` - `dark` 可用于根据系统外观自动调整 UI 主题或样式。 *** ## 电池信息 ### `Device.batteryState` 当前电池状态: ```ts "full" | "charging" | "unplugged" | "unknown" ``` 说明: - `full`:电池已充满 - `charging`:正在充电 - `unplugged`:未连接电源 - `unknown`:状态不可用 *** ### `Device.batteryLevel: number` 当前电量百分比,取值范围: - `0.0` \~ `1.0` - 当电量不可用时,可能返回 `-1` *** ## 语言与地区设置 ### `Device.systemLocale: string` 系统当前使用的 Locale,例如: ```text "en_US" ``` *** ### `Device.preferredLanguages: string[]` 用户偏好的语言列表(按优先级排序),例如: ```ts ["en-US", "zh-Hans-CN"] ``` 推荐用于多语言内容选择。 *** ### `Device.systemLocales: string[]` (已废弃) 用户偏好的 Locale 列表。 > 已废弃,请使用 `Device.preferredLanguages`。 *** ### `Device.systemLanguageTag: string` 当前语言的 BCP-47 语言标签,例如: ```text "en-US" ``` *** ### `Device.systemLanguageCode: string` 当前语言代码,例如: ```text "en" ``` *** ### `Device.systemCountryCode: string | undefined` 当前国家 / 地区代码,例如: ```text "US" ``` 如果系统未设置国家信息,可能为 `undefined`。 *** ### `Device.systemScriptCode: string | undefined` 当前语言的书写系统代码,例如: ```text "Hans" // zh_CN_Hans ``` 常用于区分简体 / 繁体等书写系统。 *** ## 唤醒锁(Wake Lock) 唤醒锁用于防止设备在脚本运行期间自动休眠。 ### `Device.isWakeLockEnabled: Promise` 获取当前是否启用了唤醒锁。 ```ts const enabled = await Device.isWakeLockEnabled ``` *** ### `Device.setWakeLockEnabled(enabled: boolean): void` 启用或关闭唤醒锁。 ```ts Device.setWakeLockEnabled(true) ``` 说明: - 仅在 **Scripting App** 中可用 - 启用后可防止屏幕自动熄灭或设备进入休眠 - 建议在不需要时及时关闭,以节省电量 *** ## 网络接口信息 ### `Device.NetworkInterface` 网络接口对象结构: ```ts type NetworkInterface = { address: string netmask: string | null family: 'IPv4' | 'IPv6' mac: string | null isInternal: boolean cidr: string | null } ``` 字段说明: - `address`:IP 地址 - `netmask`:子网掩码 - `family`:地址类型(IPv4 / IPv6) - `mac`:MAC 地址(部分系统可能为 null) - `isInternal`:是否为内部接口(如回环接口) - `cidr`:CIDR 表示法(如 `192.168.1.10/24`) *** ### `Device.networkInterfaces(): Record` 获取设备当前的网络接口信息。 返回值结构: ```ts { [interfaceName: string]: NetworkInterface[] } ``` 示例: ```ts const interfaces = Device.networkInterfaces() for (const name in interfaces) { for (const info of interfaces[name]) { console.log(name, info.address, info.family) } } ``` 常见用途: - 获取本地 IP 地址 - 区分 Wi-Fi / 蜂窝网络 / 回环接口 - 网络调试与诊断 - 模拟 Node.js `os.networkInterfaces()` 行为 *** ## 使用建议 - 语言与地区相关逻辑优先使用 `preferredLanguages` - 唤醒锁应在任务完成后及时关闭 - 网络接口信息可能因系统权限或网络状态变化而不同 - 不要假设某个接口名称一定存在(如 `en0`) --- url: /doc_v2/zh/guide/Changelog/2.4.6/Device/index_example.md --- # 示例 ```tsx import { Button, Device, List, Navigation, NavigationStack, Script, Text, VStack } from "scripting" function Example() { const dismiss = Navigation.useDismiss() const details: { name: string value: string | boolean | number }[] = [ { name: "Device.isiPhone", value: Device.isiPhone }, { name: "Device.isiPad", value: Device.isiPad, }, { name: "Device.systemVersion", value: Device.systemVersion, }, { name: "Device.systemName", value: Device.systemName, }, { name: "Device.isPortrait", value: Device.isPortrait, }, { name: "Device.isLandscape", value: Device.isLandscape, }, { name: "Device.isFlat", value: Device.isFlat, }, { name: "Device.batteryLevel", value: Device.batteryLevel, }, { name: "Device.batteryState", value: Device.batteryState, } ] return }} > {details.map(item => {item.name} {typeof item.value} )} } async function run() { await Navigation.present({ element: }) Script.exit() } run() ``` --- url: /doc_v2/zh/guide/Changelog/2.4.6/Intent.md --- # Intent Scripting 支持通过 `intent.tsx` 文件创建 iOS Intents,实现脚本与系统分享扩展(Share Sheet)和快捷指令(Shortcuts)的深度集成。你可以接收来自用户的文本、图片、文件和 URL 等输入,并返回结果供调用方使用。通过 UI 展示、数据处理与结果返回,可构建灵活且强大的自动化流程。 *** ## 一、创建和配置 Intent ### 1. 创建 Intent 脚本 1. 在 Scripting 中新建一个脚本项目。 2. 添加名为 `intent.tsx` 的文件,并编写处理逻辑和可选的 UI 组件。 ### 2. 配置支持的输入类型 点击编辑器顶部标题栏中的项目名称,打开 **Intent 设置页面**,选择该脚本支持的输入类型,如: - 文本(Text) - 图片(Image) - 文件路径(File URL) - URL 配置后,该脚本就能在分享扩展或 Shortcuts 中处理相应类型的数据。 *** ## 二、处理输入数据 在 `intent.tsx` 中,可通过以下 API 访问用户传入的数据: | 属性名 | 说明 | | -------------------------- | ---------------------------------------- | | `Intent.shortcutParameter` | Shortcuts 中传入的单个参数,包含 `.type` 和 `.value` | | `Intent.textsParameter` | 文本字符串数组 | | `Intent.urlsParameter` | URL 字符串数组 | | `Intent.imagesParameter` | 图片数组(UIImage 实例) | | `Intent.fileURLsParameter` | 文件路径数组(本地 URL) | 示例: ```ts if (Intent.shortcutParameter) { if (Intent.shortcutParameter.type === "text") { console.log(Intent.shortcutParameter.value) } } ``` *** ## 三、返回结果 使用 `Script.exit(result)` 结束脚本执行并返回结果给调用方,例如 Shortcuts 或另一个脚本。支持的返回类型包括: - 文本:`Intent.text(value)` - 富文本:`Intent.attributedText(value)` - URL:`Intent.url(value)` - JSON 数据:`Intent.json(value)` - 文件路径:`Intent.file(value)` 或 `Intent.fileURL(value)` 示例: ```ts import { Script, Intent } from "scripting" Script.exit(Intent.text("处理完成")) ``` *** ## 四、展示交互式 UI 你可以使用 `Navigation.present()` 呈现一个自定义界面,展示输入信息或收集用户反馈。在 UI 交互结束后调用 `Script.exit()` 返回结果。 示例: ```ts import { Intent, Script, Navigation, VStack, Text } from "scripting" function MyIntentView() { return ( {Intent.textsParameter?.[0]} ) } async function run() { await Navigation.present() Script.exit() } run() ``` *** ## 五、在分享扩展中使用 当脚本项目启用了对应类型的输入支持,Scripting 会自动集成到系统分享菜单: 1. 用户选中内容(如 Safari 中的文字或图片),点击分享按钮。 2. 分享列表中选择 **Scripting**。 3. 显示支持当前输入类型的脚本列表,供用户执行。 *** ## 六、与 Shortcuts 集成 你可以在 Shortcuts 应用中调用 Scripting 脚本: - **运行脚本(Run Script)**:后台执行,无 UI。 - **在 App 中运行脚本(Run Script in App)**:前台执行,支持 UI 展示。 操作步骤: 1. 在 Shortcuts 中添加 “Run Script” 或 “Run Script in App” 操作。 2. 选择目标脚本。 3. 配置参数,执行脚本。 *** ## 七、Intent API 参考 ### `Intent` 类属性 | 属性 | 类型 | 说明 | | ------------------- | ------------------- | ------------------------------------- | | `shortcutParameter` | `ShortcutParameter` | Shortcuts 传入的参数对象,包含 `type` 和 `value` | | `textsParameter` | `string[]` | 文本输入数组 | | `urlsParameter` | `string[]` | URL 字符串数组 | | `imagesParameter` | `UIImage[]` | 图片数组(路径或图片对象) | | `fileURLsParameter` | `string[]` | 文件路径数组(本地 URL) | ### `Intent` 类方法 | 方法 | 返回类型 | 示例 | | ------------------------------ | --------------------------- | ------------------------------------- | | `Intent.text(value)` | `IntentTextValue` | `Intent.text("内容")` | | `Intent.attributedText(value)` | `IntentAttributedTextValue` | `Intent.attributedText("富文本")` | | `Intent.url(value)` | `IntentURLValue` | `Intent.url("https://example.com")` | | `Intent.json(value)` | `IntentJsonValue` | `Intent.json({ key: "value" })` | | `Intent.file(filePath)` | `IntentFileValue` | `Intent.file("/path/to/file.txt")` | | `Intent.fileURL(filePath)` | `IntentFileURLValue` | `Intent.fileURL("/path/to/file.pdf")` | | `Intent.image(UIImage)` | `IntentImageValue` | `Intent.image(uiImage)` | *** ## 八、最佳实践与注意事项 - 所有脚本应显式调用 `Script.exit()` 以确保内存安全。 - 推荐在 UI 脚本中使用 `await Navigation.present()` 之后再调用 `Script.exit()`。 - 对于大文件或图像,建议使用 “Run Script in App” 模式,以避免系统内存限制导致的崩溃。 - 如果脚本需要共享数据,可通过 URL Scheme 或 `queryParameters` 实现。 --- url: /doc_v2/zh/guide/Changelog/2.4.6/ItemProvider.md --- # ItemProvider `ItemProvider` 用于表示一个**可按需加载的数据提供者**,常见于拖放、文件导入、Photos 选择等场景。 它并不直接包含数据本身,而是描述**可以如何、安全地获取数据**。 `ItemProvider` 支持加载对象、文本、URL、原始数据以及文件路径,并对文件访问施加了明确的安全访问边界。 *** ## 核心概念 - `ItemProvider` 描述的是能力,而不是数据 - 所有加载行为都必须遵循系统的安全作用域规则 - 文件类资源只能在受控的回调作用域内访问 - 是否支持原地访问(in-place)由底层系统决定 *** ## 属性 ### registeredTypes ```ts readonly registeredTypes: UTType[] ``` 表示该 `ItemProvider` 在语义上可以提供的所有类型。 - 包含直接类型以及可推导的父类型 - 用于判断内容大类或调试用途 - 不保证一定存在对应的底层文件表示 *** ### registeredInPlaceTypes ```ts readonly registeredInPlaceTypes: UTType[] ``` 表示该 `ItemProvider` 支持原地访问(open-in-place)的类型集合。 - 常见于视频、音频、文档等大文件 - 是否真正原地访问需以加载结果为准 *** ## 能力判断方法 ### hasItemConforming ```ts hasItemConforming(type: UTType): boolean ``` 判断内容在语义上是否符合指定类型。 - 判断宽松 - 会考虑 UTType 的继承关系 - 适合用于业务分支判断 *** ### hasRepresentationConforming ```ts hasRepresentationConforming(type: UTType): boolean ``` 判断是否存在一个真实的、可加载的底层表示符合指定类型。 - 判断严格 - 适合用于文件处理或精确格式要求的场景 *** ### hasInPlaceRepresentationConforming ```ts hasInPlaceRepresentationConforming(type: UTType): boolean ``` 判断是否存在支持原地访问的底层表示。 - 常用于大文件加载策略选择 *** ## 对象加载能力判断 ### canLoadUIImage ```ts canLoadUIImage(): boolean ``` 判断是否可以加载为 `UIImage` 对象。 - 适合 UI 展示 - 不保证原始文件格式或元数据 *** ### canLoadLivePhoto ```ts canLoadLivePhoto(): boolean ``` 判断是否可以加载为 `LivePhoto` 对象。 - 用于区分静态图片与 Live Photo - 返回 `true` 时可调用 `loadLivePhoto` *** ## 加载方法 ### loadUIImage ```ts loadUIImage(): Promise ``` 加载一个 `UIImage` 对象。 - 适合轻量展示 - 不适合用于文件级处理或资源保真 *** ### loadLivePhoto ```ts loadLivePhoto(): Promise ``` 加载一个 `LivePhoto` 对象。 - 包含图片与配对视频 - 适合展示、保存或进一步处理 *** ### loadURL ```ts loadURL(): Promise ``` 加载一个 URL 字符串。 - 可能是网页 URL - 也可能是文件 URL *** ### loadText ```ts loadText(): Promise ``` 加载纯文本内容。 - 支持 plain text - 富文本会被降级为纯文本 *** ### loadData ```ts loadData(type: UTType): Promise ``` 加载指定类型的原始二进制数据。 - 数据会整体加载进内存 - 适合 JSON、配置文件、小体积资源 - 不适合视频、音频等大文件 *** ## 文件路径加载(安全作用域) 文件路径的加载需要遵循系统的安全限制,所有文件访问都必须在指定的回调作用域内完成。 *** ### loadFilePath ```ts loadFilePath(type: UTType): Promise ``` 加载指定类型的文件路径,如果文件不存在或无法加载,返回 `null`。如果可以加载,文件会被复制到应用组的临时目录中,并返回文件路径。 如果你不再需要文件,请删除它。 示例: ```ts const filePath = provider.loadFilePath("public.movie") ``` *** ## 创建 ItemProvider ### fromUIImage ```ts ItemProvider.fromUIImage(image: UIImage): ItemProvider ``` 从 `UIImage` 创建 `ItemProvider`。 - 仅提供静态图片能力 - 不包含 Live Photo 或原始资源信息 *** ### fromText ```ts ItemProvider.fromText(text: string): ItemProvider ``` 从文本创建 `ItemProvider`。 *** ### fromURL ```ts ItemProvider.fromURL(url: string): ItemProvider | null ``` 从 URL 字符串创建 `ItemProvider`。 - URL 不合法时返回 `null` - 支持网页 URL 与文件 URL *** ### fromFilePath ```ts ItemProvider.fromFilePath(path: string): ItemProvider ``` 从文件路径创建 `ItemProvider`。 - 保留原始文件 - 适合视频、音频、文档等资源 - 支持原地访问能力判断 *** ## 使用建议 - 使用 `hasItemConforming` 进行内容类型判断 - 使用对象加载方法进行 UI 展示 - 使用文件路径加载方法处理大文件 - 文件路径只能在 `perform` 回调作用域内访问 - 不应在回调外部延迟访问安全作用域文件 --- url: /doc_v2/zh/guide/Changelog/2.4.6/MediaComposer/MediaComposer Example.md --- # MediaComposer 示例 本示例演示如何使用 `MediaComposer` 将 **视频 + 图片 + 音频** 组合成一个最终视频文件,并导出到脚本目录中。 示例流程包括: 1. 选择音频文件 2. 选择一张图片 3. 选择一个视频 4. 构建视频时间线(Video + Image) 5. 在指定时间点插入音频 6. 导出合成后的视频 *** ## 示例代码 ```tsx import { Path, Script } from "scripting" console.present().then(() => Script.exit()) async function run() { try { const audioPath = (await DocumentPicker.pickFiles({ types: ["public.audio"] })).at(0) if (audioPath == null) { console.error("no audio") return } console.log("Audio Picked") const imageResult = (await Photos.pick({ filter: PHPickerFilter.images() })).at(0) const imagePath = await imageResult?.itemProvider.loadFilePath("public.image") if (!imagePath) { console.log("No image") return } console.log("Image picked") const videoResult = (await Photos.pick({ filter: PHPickerFilter.videos() })).at(0) const videoPath = await videoResult?.itemProvider.loadFilePath("public.movie") if (videoPath == null) { console.log("No video") return } console.log("Video Picked") console.log("Start composing...") const exportPath = Path.join( Script.directory, "dest.mp4" ) const exportResult = await MediaComposer.composeAndExport({ exportPath, timeline: { videoItems: [{ videoPath: videoPath }, { imagePath: imagePath, duration: MediaTime.make({ seconds: 5, preferredTimescale: 600 }) }], audioClips: [{ path: audioPath, at: MediaTime.make({ seconds: 5, preferredTimescale: 600 }) }] } }) console.log( "Result:", exportResult.exportPath, "\n", exportResult.duration.getSeconds() ) } catch (e) { console.error(e) } } run() ``` *** ## 时间线解析 ### 视频 / 图片时间线(videoItems) ```ts videoItems: [ { videoPath }, { imagePath, duration: MediaTime.make({ seconds: 5, preferredTimescale: 600 }) } ] ``` - 第一个 `VideoItem` 是完整视频 - 第二个 `VideoItem` 是一张图片,显示 5 秒 - 所有 `videoItems` **按顺序依次拼接** - 最终视频总时长 = 视频时长 + 图片 5 秒 *** ### 音频时间线(audioClips) ```ts audioClips: [{ path: audioPath, at: MediaTime.make({ seconds: 5, preferredTimescale: 600 }) }] ``` - 音频在最终视频的 **第 5 秒开始播放** - 不指定 `at` 时,音频会顺序接在前一个外部音频之后 - 音频不会影响最终视频时长 *** ## 导出结果 ```ts { exportPath: string duration: MediaTime } ``` - `exportPath`:导出文件的完整路径 - `duration`:最终视频时长(仅由 `videoItems` 决定) *** ## 常见错误与边界情况 ### 1. ImageClip 未指定 duration ```ts { imagePath: "...", // 缺少 duration } ``` **问题:** - ImageClip 没有天然时长 - 不指定 `duration` 会导致合成失败 **解决方案:** - 必须显式提供 `MediaTime` *** ### 2. 使用浮点秒数而非 MediaTime ```ts // 错误 at: 5 ``` **正确做法:** ```ts at: MediaTime.make({ seconds: 5, preferredTimescale: 600 }) ``` MediaComposer 中 **所有时间必须使用 MediaTime**。 *** ### 3. 混合不同 timescale 导致精度问题 **问题:** - 不同音视频资源使用不同 timescale - 在剪辑、拼接、淡入淡出时可能出现边界误差 **建议:** - 在脚本中统一使用 `preferredTimescale: 600` - 对外部时间先做 `convertScale` *** ### 4. 音频超出视频范围 **行为说明:** - 音频即使超过视频末尾,也不会延长最终视频 - 超出部分会被自动截断 *** ### 5. 同时存在视频原音与外部音频但音量异常 **原因:** - 默认情况下,外部音频与视频原音会同时混合 - 未配置 ducking 时,可能出现人声被盖住的问题 *** ## 音频 Ducking 行为说明 ### 什么是 Ducking Ducking 指的是: > 当视频原音(如人声)存在时,自动降低外部音频(如背景音乐)的音量 *** ### Ducking 配置 ```ts exportOptions: { ducking: { enabled: true, duckedVolume: 0.25, attackSeconds: 0.15, releaseSeconds: 0.25 } } ``` #### 参数说明 - **enabled** 是否启用 ducking,默认 `true` - **duckedVolume** 被压低后的外部音频音量(0…1) - **attackSeconds** 在视频原音开始前,音量下降的过渡时间 - **releaseSeconds** 在视频原音结束后,音量恢复的过渡时间 *** ### Ducking 生效条件 Ducking 仅在以下条件同时满足时生效: 1. `VideoClip.keepOriginalAudio === true` 2. 存在外部 `audioClips` 3. `exportOptions.ducking.enabled !== false` *** ## 音频混音规则总结 1. **视频原音** - 只有在 `keepOriginalAudio: true` 时才参与混音 2. **外部音频** - 可指定时间点或顺序拼接 - 可设置 `volume`、`fade`、`loopToFitVideoDuration` 3. **最终混音顺序** - 所有音频会被混合到单一音轨 - 不会改变视频时长 - Ducking 在混音阶段自动应用 --- url: /doc_v2/zh/guide/Changelog/2.4.6/MediaComposer/MediaTime.md --- # MediaTime `MediaTime` 用于表示音视频处理中的**精确时间点或时间长度**,是 Scripting 中 MediaComposer 时间系统的基础类型。 它在语义上对应于底层媒体框架中的“带时间基准的时间值”(如 AVFoundation 的 `CMTime`),但对脚本侧提供了更安全、可读、可计算的抽象。 `MediaTime` 既可以表示**确定的数值时间**,也可以表示**无效、无限或不确定时间**,并支持严格的时间运算与比较。 *** ## 核心特性 - 使用 **value + timescale** 或 **seconds + preferredTimescale** 精确构造时间 - 支持时间缩放(convertScale)及多种舍入策略 - 支持加减运算与大小比较 - 明确区分有效时间、无效时间、无限时间和不确定时间 - 适用于时间线计算、剪辑、对齐、放置(at)、淡入淡出等所有时间相关场景 *** ## 时间精度模型 `MediaTime` 的底层模型基于以下概念: - **value**:整数时间值 - **timescale**:每秒的时间单位数 例如: - `value = 300`, `timescale = 600` 表示 0.5 秒 - `value = 18000`, `timescale = 600` 表示 30 秒 通过 timescale,`MediaTime` 可以精确表达帧级或采样级时间,而不依赖浮点数。 *** ## 只读属性 ### secondes ```ts readonly secondes: number ``` 当前时间对应的秒数(浮点数形式)。 这是一个**派生值**,主要用于展示或调试,不建议用于时间计算。 *** ### isValid ```ts readonly isValid: boolean ``` 表示该时间是否是一个有效、可用于计算的时间值。 当时间为 `invalid`、`indefinite` 或无穷大时,该值为 `false`。 *** ### isPositiveInfinity / isNegativeInfinity ```ts readonly isPositiveInfinity: boolean readonly isNegativeInfinity: boolean ``` 表示该时间是否为正无穷或负无穷。 常用于内部边界标记或时间线计算中的极值判断。 *** ### isIndefinite ```ts readonly isIndefinite: boolean ``` 表示该时间是否为“不确定时间”。 通常用于尚未解析出真实时长的媒体资源。 *** ### isNumeric ```ts readonly isNumeric: boolean ``` 表示该时间是否是一个可参与数值计算的时间。 只有在该值为 `true` 时,才应进行加减或比较操作。 *** ### hasBeenRounded ```ts readonly hasBeenRounded: boolean ``` 表示该时间是否在构造或转换过程中发生过舍入。 对于帧精度或采样精度要求较高的场景,该属性可用于调试或验证。 *** ## 时间转换 ### convertScale ```ts convertScale(newTimescale: number, method: MediaTimeRoundingMethod): MediaTime ``` 将当前时间转换为新的 timescale,并使用指定的舍入策略。 **典型用途:** - 对齐视频帧时间(如 600、90000) - 对齐音频采样时间(如 44100、48000) - 避免不同时间基准混用导致的误差 *** ## 时间值获取 ### getSeconds ```ts getSeconds(): number ``` 返回当前时间对应的秒数(浮点数)。 该方法等价于读取 `secondes`,但在语义上更明确。 *** ## 时间运算 ### plus / minus ```ts plus(other: MediaItem): MediaItem minus(other: MediaItem): MediaItem ``` 执行时间加法或减法运算,返回新的 `MediaTime`。 - 运算双方必须为可计算时间 - 不会修改原对象 - 运算结果遵循内部时间基准规则 *** ## 时间比较 ```ts lt(other: MediaItem): boolean gt(other: MediaItem): boolean lte(other: MediaItem): boolean gte(other: MediaItem): boolean eq(other: MediaItem): boolean neq(other: MediaItem): boolean ``` 用于比较两个时间的大小或相等性。 - 支持严格比较 - 对无效或非数值时间的比较结果是确定性的 - 推荐在进行时间线排序、裁剪判断、边界检测时使用 *** ## 静态构造方法 ### make ```ts static make(options: { value: number timescale: number } | { seconds: number preferredTimescale: number }): MediaTime ``` 用于创建一个 `MediaTime` 实例。 #### 使用 value + timescale ```ts MediaTime.make({ value: 300, timescale: 600 }) ``` 适用于需要精确控制时间单位的场景。 #### 使用 seconds + preferredTimescale ```ts MediaTime.make({ seconds: 5, preferredTimescale: 600 }) ``` 适用于脚本层以“秒”为主的时间描述方式。 *** ### zero ```ts static zero(): MediaTime ``` 返回一个表示 **0 秒** 的时间。 *** ### invalid ```ts static invalid(): MediaTime ``` 返回一个无效时间。 用于显式表示错误、缺失或不可用的时间值。 *** ### indefinite ```ts static indefinite(): MediaTime ``` 返回一个不确定时间。 通常用于媒体尚未加载完成、时长未知的状态。 *** ### positiveInfinity / negativeInfinity ```ts static positiveInfinity(): MediaTime static negativeInfinity(): MediaTime ``` 返回正无穷或负无穷时间。 主要用于内部时间线边界控制,不建议在普通脚本逻辑中使用。 *** ## 使用建议与注意事项 - **避免直接使用浮点秒数进行时间计算**,应始终使用 `MediaTime` - 不同媒体资源可能使用不同的 timescale,必要时显式调用 `convertScale` - 在比较或运算前,建议检查 `isNumeric` - 在构建时间线(如 `at`、`sourceTimeRange`)时,统一 timescale 可减少误差 *** ## 在 MediaComposer 中的典型用途 - 指定音频或视频片段的放置时间(`AudioClip.at`) - 定义剪辑的起点与时长(`TimeRange`) - 计算最终导出视频的精确时长 - 控制淡入淡出、对齐、循环等时间行为 --- url: /doc_v2/zh/guide/Changelog/2.4.6/MediaComposer/Quick Start.md --- # 快速开始 `MediaComposer` 用于在 Scripting 中 **组合视频、图片与音频时间线并导出最终媒体文件**。 它封装了一套稳定的时间线模型,支持视频剪辑、图片片段、音频叠加、淡入淡出、音频 ducking、导出参数控制等高级能力。 该模块适用于: - 视频与图片混合生成短片 - 给视频添加背景音乐、配音或音效 - 使用图片序列生成视频 - 自动化视频处理与内容生成脚本 *** ## 设计概览 MediaComposer 的核心由三部分组成: 1. **时间模型** 使用 `MediaTime` / `TimeRange` 精确描述时间点与时长 2. **时间线模型** - `VideoItem[]`:视频或图片片段(顺序拼接) - `AudioClip[]`:音频轨道(可指定时间点或自动顺序放置) 3. **导出系统** 通过统一的 `composeAndExport` 接口完成渲染与导出 *** ## 时间线结构 ```ts timeline: { videoItems: VideoItem[] audioClips: AudioClip[] } ``` - **videoItems** 定义视觉时间线,视频与图片会严格按数组顺序依次排列 - **audioClips** 定义音频时间线,可自由指定放置时间(`at`),或顺序追加 最终导出的视频时长由 **videoItems 决定**。 *** ## VideoItem ```ts type VideoItem = XOR ``` `VideoItem` 表示时间线中的一个“视觉片段”,可以是 **视频** 或 **图片**,但不能同时是两者。 *** ## VideoClip(视频片段) ```ts type VideoClip = { videoPath: string sourceTimeRange?: TimeRange | null keepOriginalAudio?: boolean fade?: FadeConfig | null } ``` ### videoPath - 视频文件路径 - 支持本地视频文件 *** ### sourceTimeRange ```ts sourceTimeRange?: TimeRange | null ``` - 指定从源视频中使用的时间范围 - 不提供时,默认使用整个视频 **常见用途:** - 裁剪视频片段 - 只取某一段作为素材 *** ### keepOriginalAudio ```ts keepOriginalAudio?: boolean ``` - 是否保留视频自带的音频 - 默认值:`false` **说明:** - 为 `true` 时,视频原音会参与混音 - 可与外部 `audioClips` 同时存在 - 是否对外部音频进行 ducking 由 `ExportOptions.ducking` 控制 *** ### fade ```ts fade?: FadeConfig | null ``` - 视频片段的淡入淡出配置 - 会覆盖全局视频淡入淡出设置(如果存在) *** ## ImageClip(图片片段) ```ts type ImageClip = { imagePath: string duration: MediaTime contentMode?: "fit" | "crop" backgroundColor?: Color fade?: FadeConfig | null } ``` `ImageClip` 用于将一张静态图片作为视频时间线中的一个片段。 *** ### imagePath - 图片文件路径 - 支持常见图片格式(JPEG / PNG / HEIC 等) *** ### duration ```ts duration: MediaTime ``` - 图片片段在视频中的显示时长 - 必须显式指定 *** ### contentMode ```ts contentMode?: "fit" | "crop" ``` - 控制图片如何适配渲染尺寸 - 默认值:`fit` 说明: - `fit`:完整显示图片,可能留黑边 - `crop`:填满画面,超出部分裁剪 *** ### backgroundColor ```ts backgroundColor?: Color ``` - 图片未覆盖区域的背景色 - 通常与 `fit` 模式搭配使用 *** ### fade ```ts fade?: FadeConfig | null ``` - 图片片段的淡入淡出配置 - 支持与视频片段统一使用 *** ## AudioClip(音频片段) ```ts type AudioClip = { path: string sourceTimeRange?: TimeRange | null at?: MediaTime volume?: number fade?: FadeConfig | null loopToFitVideoDuration?: boolean } ``` 音频片段用于在最终视频中添加背景音乐、配音或音效。 *** ### path - 音频文件路径 *** ### sourceTimeRange - 指定使用音频的某一时间段 - 默认使用整个音频文件 *** ### at ```ts at?: MediaTime ``` - 指定音频在最终时间线中的放置时间 - 不指定时: - 按顺序接在前一个外部音频片段之后 *** ### volume ```ts volume?: number ``` - 单个音频片段的音量(0…1) - 默认值:1 *** ### fade - 音频淡入淡出配置 - 常用于背景音乐的自然过渡 *** ### loopToFitVideoDuration ```ts loopToFitVideoDuration?: boolean ``` - 是否循环音频以匹配视频总时长 - 常用于背景音乐 *** ## FadeConfig(淡入淡出) ```ts type FadeConfig = { fadeInSeconds?: number fadeOutSeconds?: number } ``` - 单位:秒 - 可用于视频、图片、音频 - 未指定时默认为 0 *** ## ExportOptions(导出配置) ```ts type ExportOptions = { renderSize?: Size frameRate?: number scaleMode?: VideoScaleMode globalVideoFade?: FadeConfig | null externalAudioBaseVolume?: number ducking?: DuckingConfig presetName?: ExportPreset outputFileType?: ExportFileType } ``` ### 常用说明 - **renderSize** 最终视频分辨率,默认 1080×1920 - **frameRate** 渲染帧率,默认 30 - **globalVideoFade** 全局视频淡入淡出(可被单个 clip 覆盖) - **ducking** 当视频存在原音时,自动降低外部音频音量 - **presetName / outputFileType** 控制编码质量与文件格式 *** ## composeAndExport ```ts function composeAndExport(options: { exportPath: string timeline: { videoItems: VideoItem[] audioClips: AudioClip[] } exportOptions?: ExportOptions overwrite?: boolean }): Promise<{ exportPath: string duration: MediaTime }> ``` ### 参数说明 - **exportPath** 导出文件路径 - **timeline.videoItems** 视频 / 图片时间线(顺序执行) - **timeline.audioClips** 音频时间线(可自由放置) - **exportOptions** 导出配置,可选 - **overwrite** 是否覆盖已有文件,默认 `true` *** ### 返回结果 ```ts { exportPath: string duration: MediaTime } ``` - **exportPath**:最终导出路径 - **duration**:最终视频时长(由 videoItems 决定) *** ## 使用建议与最佳实践 - 始终使用 `MediaTime` 描述时间,避免直接使用浮点秒数 - 图片片段必须显式指定 `duration` - 音频与视频的时间线是 **独立但最终混合** 的 - 对复杂项目,建议统一 timescale(如 600) - 背景音乐推荐使用 `loopToFitVideoDuration` *** ## 典型使用场景 - 图片 + 视频混合短片 - 自动生成带背景音乐的视频 - 视频剪辑与配音合成 - 内容创作与自动化视频生成 --- url: /doc_v2/zh/guide/Changelog/2.4.6/SharedAudioSession.md --- # SharedAudioSession 通过 `SharedAudioSession`,你可以在脚本中方便地管理和操作共享音频会话(audio session)。音频会话充当脚本、Scripting 应用、操作系统和底层音频硬件之间的中介,允许你有效地配置和控制音频的行为。 *** ## 功能简介 - 获取和设置音频会话的类别(category)、模式(mode)和选项(options)。 - 配置音频输入和输出的首选采样率(sample rate)。 - 处理音频中断事件。 - 查询设备所支持的类别和模式。 - 根据具体的应用场景(如视频录制、语音聊天、后台播放等)来定制音频行为。 *** ## 方法和属性 ### 1. **会话类别与选项** #### `category` 获取当前音频会话的类别(Category)。 ```typescript const category = await SharedAudioSession.category console.log(category) // 示例输出:'playback' ``` #### `categoryOptions` 获取当前音频会话类别的选项(Options)。 ```typescript const options = await SharedAudioSession.categoryOptions console.log(options) // 示例输出:['mixWithOthers', 'allowAirPlay'] ``` #### `setCategory(category: AudioSessionCategory, options: AudioSessionCategoryOptions[])` 设置音频会话的类别并指定其选项。 ```typescript await SharedAudioSession.setCategory('playback', ['mixWithOthers']) ``` *** ### 2. **会话模式** #### `mode` 获取当前音频会话模式(Mode)。 ```typescript const mode = await SharedAudioSession.mode console.log(mode) // 示例输出:'videoChat' ``` #### `setMode(mode: AudioSessionMode)` 设置音频会话模式。 ```typescript await SharedAudioSession.setMode('voiceChat') ``` *** ### 3. **采样率 (Sample Rate)** #### `preferredSampleRate` 获取当前首选采样率(单位为 Hz)。 ```typescript const sampleRate = await SharedAudioSession.preferredSampleRate console.log(sampleRate) // 示例输出:44100 ``` #### `setPreferredSampleRate(sampleRate: number)` 设置音频输入和输出的首选采样率。 ```typescript await SharedAudioSession.setPreferredSampleRate(48000) ``` *** ### 4. **音频中断处理** #### `addInterruptionListener(listener: AudioSessionInterruptionListener)` 监听音频中断事件。 ```typescript SharedAudioSession.addInterruptionListener((type) => { if (type === 'began') { console.log('音频中断开始') } else if (type === 'ended') { console.log('音频中断结束') } }) ``` #### `removeInterruptionListener(listener: AudioSessionInterruptionListener)` 移除音频中断监听器。 ```typescript SharedAudioSession.removeInterruptionListener(myListener) ``` *** ### 5. **设备功能查询** #### `availableCategories` 获取设备上可用的音频会话类别列表。 ```typescript const categories = await SharedAudioSession.availableCategories console.log(categories) // 示例输出:['playback', 'record', 'soloAmbient'] ``` #### `availableModes` 获取设备上可用的音频会话模式列表。 ```typescript const modes = await SharedAudioSession.availableModes console.log(modes) // 示例输出:['default', 'videoChat', 'voiceChat'] ``` *** ### 6. **其他属性** #### `isOtherAudioPlaying` 检查设备上是否有其他音频正在播放。 ```typescript const isPlaying = await SharedAudioSession.isOtherAudioPlaying console.log(isPlaying) // 示例输出:true ``` #### `secondaryAudioShouldBeSilencedHint` 检查次要音频是否应该被静音。 ```typescript const shouldSilence = await SharedAudioSession.secondaryAudioShouldBeSilencedHint console.log(shouldSilence) // 示例输出:false ``` #### `allowHapticsAndSystemSoundsDuringRecording` 检查录音期间是否允许触觉反馈和系统声音。 ```typescript const allowHaptics = await SharedAudioSession.allowHapticsAndSystemSoundsDuringRecording console.log(allowHaptics) // 示例输出:true ``` #### `prefersNoInterruptionsFromSystemAlerts` 检查音频会话是否偏好不被系统警报打断。 ```typescript const prefersNoInterruptions = await SharedAudioSession.prefersNoInterruptionsFromSystemAlerts console.log(prefersNoInterruptions) // 示例输出:false ``` *** ### 7. **会话激活** #### `setActive(active: boolean, options?: AudioSessionSetActiveOptions[])` 激活或停用共享音频会话,可指定激活选项。 ```typescript await SharedAudioSession.setActive( true, ['notifyOthersOnDeactivation'] ) ``` *** ### 8. **系统设置** #### `setAllowHapticsAndSystemSoundsDuringRecording(value: boolean)` 启用或禁用在录音期间允许触觉反馈和系统声音。 ```typescript await SharedAudioSession.setAllowHapticsAndSystemSoundsDuringRecording(true) ``` #### `setPrefersNoInterruptionsFromSystemAlerts(value: boolean)` 设置是否偏好不被系统警报打断。 ```typescript await SharedAudioSession.setPrefersNoInterruptionsFromSystemAlerts(true) ``` *** ### 9. **系统输出音量** #### `outputVolume: number` 获取当前系统输出音量(范围为 0 到 1)。 #### outputVolume 监听事件 类型类型 ```ts type AudioSessionOutputVolumeListener = (newValue: number, oldValue: number) => void ``` ##### `addOutputVolumeListener(listener: AudioSessionOutputVolumeListener): void` 添加系统输出音量监听器。 ##### `removeOutputVolumeListener(listener: AudioSessionOutputVolumeListener): void` 移除系统输出音量监听器。 *** ## 枚举(Enumerations) ### **AudioSessionSetActiveOptions** 定义激活选项: - `'notifyOthersOnDeactivation'` ### **AudioSessionCategory** 定义音频会话的类别: - `'ambient'` - `'multiRoute'` - `'playAndRecord'` - `'playback'` - `'record'` - `'soloAmbient'` ### **AudioSessionCategoryOptions** 定义类别的可选行为: - `'mixWithOthers'` - `'duckOthers'` - `'interruptSpokenAudioAndMixWithOthers'` - `'allowBluetooth'` - `'allowBluetoothA2DP'` - `'allowAirPlay'` - `'defaultToSpeaker'` - `'overrideMutedMicrophoneInterruption'` ### **AudioSessionMode** 指定会话模式: - `'default'` - `'gameChat'` - `'measurement'` - `'moviePlayback'` - `'spokenAudio'` - `'videoChat'` - `'videoRecording'` - `'voiceChat'` - `'voicePrompt'` ### **AudioSessionInterruptionType** 指定中断类型: - `'began'` - `'ended'` - `'unknown'` *** 通过此接口,你可以在 Scripting 应用中对音频会话进行深度管理,非常适合构建对音频依赖较高的脚本,如音乐播放器和视频会议工具等。 --- url: /doc_v2/zh/guide/Changelog/2.4.6/VideoRecorder.md --- # 视频录制(VideoRecorder) `VideoRecorder` 用于在 Scripting 中创建和控制一个可编程的视频录制会话,完整封装了相机选择、音视频采集、编码、暂停/恢复、缩放、对焦、补光灯控制以及最终文件写入流程。 该 API 适用于自定义相机界面、视频采集工具、自动化录制脚本等场景。 *** ## 核心能力概览 - 支持前置与后置摄像头 - 支持指定摄像头类型(广角、超广角、长焦等) - 支持多种帧率(24 / 30 / 60 / 120) - 支持音频录制的启用与禁用 - 支持多种系统 Session Preset - 支持多种视频编码格式(HEVC / H.264 / ProRes 等) - 支持录制过程中的暂停与恢复 - 支持对焦点、曝光点独立控制 - 支持变焦与平滑变焦(ramp) - 支持补光灯(Torch)控制 - 明确的状态机与状态回调 - 明确的生命周期管理(prepare / reset / dispose) *** ## 类型定义 ### CameraPosition ```ts type CameraPosition = "front" | "back" ``` 表示使用的摄像头朝向。 *** ### CameraType ```ts type CameraType = | "wide" | "ultraWide" | "telephoto" | "trueDepth" | "dual" | "dualWide" | "triple" ``` 表示摄像头的物理类型。 是否支持某一类型取决于设备硬件能力。 *** ### VideoRecorderState ```ts type VideoRecorderState = | "idle" | "preparing" | "ready" | "recording" | "paused" | "finishing" | "finished" | "failed" ``` #### 状态说明 | 状态 | 含义 | | ----------- | -------------- | | `idle` | 初始状态,尚未配置资源 | | `preparing` | 正在配置相机会话与音视频管线 | | `ready` | 已准备完成,可以开始录制 | | `recording` | 正在录制 | | `paused` | 录制已暂停 | | `finishing` | 正在结束录制并写入文件 | | `finished` | 录制完成 | | `failed` | 发生错误,录制失败 | *** ### VideoCaptureSessionPreset ```ts type VideoCaptureSessionPreset = | "high" | "medium" | "low" | "cif352x288" | "vga640x480" | "iFrame960x540" | "iFrame1280x720" | "hd1280x720" | "hd1920x1080" | "hd4K3840x2160" ``` 表示捕捉会话的分辨率预设。 *** ### VideoCodec ```ts type VideoCodec = | "hevc" | "h264" | "jpeg" | "JPEGXL" | "proRes4444" | "appleProRes4444XQ" | "proRes422" | "proRes422HQ" | "proRes422LT" | "proRes422Proxy" | "proResRAW" | "proResRAWHQ" | "hevcWithAlpha" ``` 表示视频编码格式。 具体是否可用取决于设备与系统支持情况。 *** ### VideoOrientation ```ts type VideoOrientation = | "portrait" | "landscapeLeft" | "landscapeRight" ``` 表示输出视频的方向。 *** ## 构造函数 ```ts new VideoRecorder(settings?) ``` ### settings 参数 ```ts { camera?: { position: CameraPosition preferredTypes?: CameraType[] } frameRate?: number audioEnabled?: boolean sessionPreset?: VideoCaptureSessionPreset videoCodec?: VideoCodec videoBitRate?: number orientation?: VideoOrientation mirrorFrontCamera?: boolean } ``` #### 参数说明 - **camera** - `position` 使用的摄像头位置,默认 `"back"` - `preferredTypes` 偏好的摄像头类型列表 如果未提供,内部将根据摄像头位置自动选择合适的类型 - **frameRate** 目标帧率,支持 24 / 30 / 60 / 120,默认 30 实际帧率受设备与分辨率限制 - **audioEnabled** 是否录制音频,默认 `true` - **sessionPreset** 捕捉会话分辨率预设,默认 `"high"` - **videoCodec** 视频编码格式,默认 `"hevc"` - **videoBitRate** 视频平均码率(bit/s),未指定时由系统自动选择 - **orientation** 输出视频方向,默认 `"portrait"` - **mirrorFrontCamera** 前置摄像头是否镜像,默认 `true` 仅在使用前置摄像头时生效 *** ## 只读属性 ### minZoomFactor ```ts readonly minZoomFactor: number ``` 当前设备支持的最小变焦倍率。 *** ### maxZoomFactor ```ts readonly maxZoomFactor: number ``` 当前设备支持的最大变焦倍率。 *** ### currentZoomFactor ```ts readonly currentZoomFactor: number ``` 当前变焦倍率。 *** ### displayZoomFactor ```ts readonly displayZoomFactor: number ``` 用户可读的变焦倍率。 *** ### hasTorch ```ts readonly hasTorch: boolean ``` 当前摄像头是否支持补光灯。 *** ### torchMode ```ts readonly torchMode: "auto" | "on" | "off" ``` 当前补光灯模式。 *** ## 状态与回调 ### state ```ts state: VideoRecorderState ``` 表示当前录制器所处的状态。 *** ### onStateChanged ```ts onStateChanged?: ( state: VideoRecorderState, details?: string ) => void ``` 状态变化回调。 - 当 `state === "failed"` `details` 为错误信息描述 - 当 `state === "finished"` `details` 为最终视频文件的完整保存路径 *** ## 方法说明 ### prepare() ```ts prepare(): Promise ``` 准备录制器资源,包括相机会话、输入输出与音视频管线配置。 #### 使用约束 - 必须在 `startRecording` 之前调用 - 成功后状态变为 `ready` - 失败会进入 `failed` 状态 *** ### startRecording(toPath) ```ts startRecording(toPath: string): void ``` 开始录制视频。 #### 参数 - **toPath** 视频文件保存路径(完整路径) #### 使用约束 - 仅允许在 `ready` 状态下调用 - 调用后状态变为 `recording` *** ### pauseRecording() ```ts pauseRecording(): void ``` 暂停当前录制。 #### 使用约束 - 仅允许在 `recording` 状态下调用 - 调用后状态变为 `paused` - 时间线会被正确压缩,不产生空白帧 *** ### resumeRecording() ```ts resumeRecording(): void ``` 恢复已暂停的录制。 #### 使用约束 - 仅允许在 `paused` 状态下调用 - 调用后状态回到 `recording` *** ### stopRecording() ```ts stopRecording(): Promise ``` 停止录制并生成最终视频文件。 #### 行为说明 - 状态先进入 `finishing` - 文件写入完成后进入 `finished` - 最终文件路径通过 `onStateChanged` 回调返回 *** ### reset() ```ts reset(): Promise ``` 重置录制器状态,用于开始新一轮录制。 #### 使用场景 - 已完成一次录制 - 或录制失败后希望复用同一实例 #### 行为说明 - 成功后状态回到 `idle` - 可再次调用 `prepare` *** ### setTorchMode() ```ts setTorchMode(mode: "auto" | "off" | "on"): void ``` 设置补光灯模式。 *** ### setFocusPoint() ```ts setFocusPoint(point: { x: number; y: number }): void ``` 设置对焦点。 - 坐标为归一化值,范围 `0.0 ~ 1.0` - `(0,0)` 表示画面左上角,`(1,1)` 表示右下角 *** ### setExposurePoint() ```ts setExposurePoint(point: { x: number; y: number }): void ``` 设置曝光点,坐标规则与对焦点一致。 *** ### resetFocus() ```ts resetFocus(): void ``` 恢复自动对焦模式。 *** ### resetExposure() ```ts resetExposure(): void ``` 恢复自动曝光模式。 *** ### setZoomFactor() ```ts setZoomFactor(factor: number): void ``` 立即设置变焦倍率。 - 值应位于 `minZoomFactor` 与 `maxZoomFactor` 之间 *** ### rampZoomFactor() ```ts rampZoomFactor(toFactor: number, rate: number): void ``` 以平滑方式调整变焦倍率。 - `toFactor` 为目标倍率 - `rate` 为变化速率,单位为每秒的 2 次幂变化 *** ### resetZoom() ```ts resetZoom(): void ``` 将变焦倍率恢复为默认值(通常为 1.0)。 *** ### dispose() ```ts dispose(): Promise ``` 释放并销毁录制器。 #### 使用约束 - 调用后实例不可再使用 - 会释放相机、音频与底层系统资源 - 建议在页面或脚本生命周期结束时调用 *** ## 典型使用流程 ```ts const recorder = new VideoRecorder({ camera: { position: "back" }, frameRate: 60, videoCodec: "hevc" }) recorder.onStateChanged = (state, details) => { if (state === "finished") { console.log("Video saved at:", details) } } await recorder.prepare() recorder.startRecording("/path/to/tmp/demo.mov") ``` *** ## 使用建议 - 始终监听 `onStateChanged` 以掌握完整状态变化 - 不要在未调用 `prepare` 的情况下开始录制 - 每次完成录制后,如需复用实例,应先调用 `reset` - 生命周期结束时务必调用 `dispose` 释放资源 --- url: /doc_v2/zh/guide/Changelog/2.4.6/onDrag and onDrop View Modifiers.md --- # onDrag 和 onDrop 修饰符 Scripting 提供了一套与 SwiftUI Drag & Drop 行为模型高度一致的 API,用于在脚本中实现视图间、应用内或跨应用的拖拽与放置操作。 该能力主要由以下三部分构成: - **onDrag**:将当前视图声明为拖拽源 - **onDrop**:将当前视图声明为放置目标 - **DropInfo / ItemProvider / UTType**:描述拖拽内容与状态的上下文对象 拖拽与放置是一个严格受系统控制的交互流程,部分 API 只能在特定回调中调用,文档中会明确指出这些限制。 *** ## 核心数据类型 ### DropInfo `DropInfo` 描述一次拖拽在当前放置视图上的实时状态。该对象仅在 `onDrop` 相关回调中有效。 #### 属性 ##### location: Point - 表示拖拽当前位置 - 坐标空间为 **放置视图自身的本地坐标系** - 可用于实现基于位置的高亮、插入指示线、排序逻辑等 #### 方法 ##### hasItemsConforming(types: UTType\[]): boolean - 用于判断拖拽内容中,是否至少有一个项目符合指定的 UTType - 常用于: - `validateDrop` - `dropEntered` - `dropUpdated` - 不会实际加载数据,仅用于能力判断 ##### itemProviders(types: UTType\[]): ItemProvider\[] - 返回符合指定 UTType 的 `ItemProvider` 列表 - **仅允许在 `performDrop` 回调中调用** - 在该方法返回后,系统将撤销对拖拽数据的访问权限 > 重要约束 > 必须在 `performDrop` 方法作用域内 **立即开始** 对 ItemProvider 的数据加载(如 `loadData`、`loadText`)。 > 不允许延迟到其他回调或异步逻辑中再发起加载。 *** ## DropOperation `DropOperation` 用于描述当前拖拽更新阶段,目标视图期望执行的操作类型。 可选值如下: - `"copy"` 表示复制数据(最常见,用于文件、文本、图片等) - `"move"` 表示移动数据(通常仅用于应用内部拖拽) - `"cancel"` 取消本次拖拽,不执行任何数据传输 - `"forbidden"` 明确禁止当前拖拽行为,系统通常会显示禁止指示 `DropOperation` 通常由 `dropUpdated` 回调返回,用于动态控制拖拽行为。 *** ## DragDropProps `DragDropProps` 是所有支持拖拽与放置能力的视图可选属性集合。 *** ## onDrag ### 用途 将当前视图声明为 **拖拽源**,允许用户从该视图开始一次拖拽操作。 ### 定义 ```ts onDrag?: { data: () => ItemProvider preview: VirtualNode } ``` ### 参数说明 #### data ```ts data: () => ItemProvider ``` - 返回一个 `ItemProvider` - 用于描述拖拽时传递的数据内容 - 支持文本、图片、文件、URL、自定义类型等 - 每次拖拽开始时调用 > 建议 > 仅在该回调中构造 ItemProvider,不要复用旧实例,以确保数据状态正确。 #### preview ```ts preview: VirtualNode ``` - 指定拖拽开始后显示的预览视图 - 系统会自动将其渲染为拖拽浮层 - 预览视图默认居中于源视图 *** ## onDrop ### 用途 将当前视图声明为 **放置目标**,并通过一组回调精细控制拖拽验证、状态变化与最终数据接收。 ### 定义 ```ts onDrop?: { types: UTType[] validateDrop?: (info: DropInfo) => boolean dropEntered?: (info: DropInfo) => void dropUpdated?: (info: DropInfo) => DropOperation | null dropExited?: (info: DropInfo) => void performDrop: (info: DropInfo) => boolean } ``` *** ### onDrop.types ```ts types: UTType[] ``` - 声明该视图 **允许接收的内容类型** - 如果拖拽内容不包含任意一个匹配类型: - 放置区域不会激活 - `validateDrop` 不会被调用 - 视觉高亮不会出现 *** ### validateDrop ```ts validateDrop?: (info: DropInfo) => boolean ``` - 用于判断是否允许开始一次放置操作 - 返回 `false` 将直接拒绝拖拽 - 常见用途: - 检查类型数量 - 校验业务状态(如只允许空列表接收) 默认行为:始终返回 `true` *** ### dropEntered ```ts dropEntered?: (info: DropInfo) => void ``` - 当拖拽进入放置区域时触发 - 通常用于: - 显示高亮 - 显示插入占位符 - 触发动画状态 *** ### dropUpdated ```ts dropUpdated?: (info: DropInfo) => DropOperation | null ``` - 当拖拽在放置区域内部移动时反复调用 - 用于动态返回期望的 `DropOperation` 返回值说明: - 返回具体的 `DropOperation`:更新当前拖拽行为 - 返回 `null`: - 使用上一次返回的有效值 - 若没有历史值,默认使用 `"copy"` *** ### dropExited ```ts dropExited?: (info: DropInfo) => void ``` - 当拖拽离开放置区域时触发 - 常用于清理高亮、移除占位 UI *** ### performDrop ```ts performDrop: (info: DropInfo) => boolean ``` - **最关键的回调** - 表示用户已松手,系统允许你读取拖拽数据 - 返回值: - `true`:表示成功接收并处理了拖拽 - `false`:表示放置失败 #### 重要约束(必须遵守) - 必须在该方法作用域内: - 调用 `info.itemProviders(...)` - 并立即开始数据加载 - 不允许: - 将 ItemProvider 保存到外部 - 在异步回调中延迟访问拖拽数据 这是系统级安全限制,不遵守将导致数据无法访问。 *** ## 典型使用流程总结 1. 用户从 `onDrag` 视图开始拖拽 2. 系统根据 `onDrop.types` 判断是否激活目标 3. 调用 `validateDrop` 4. 进入放置区域 → `dropEntered` 5. 移动过程中 → 多次 `dropUpdated` 6. 离开区域 → `dropExited` 7. 松手 → `performDrop` 8. 在 `performDrop` 中读取并处理数据 *** ## 设计建议与最佳实践 - 始终精确声明 `UTType`,避免过于宽泛 - 在 `dropUpdated` 中返回 `"forbidden"` 可显式阻止非法拖拽 - 复杂数据解析逻辑应在 `ItemProvider` 加载完成后的异步回调中完成,而不是在 `performDrop` 中同步阻塞 - 跨应用拖拽时,优先使用系统标准类型(text、image、file、url) --- url: /doc_v2/zh/guide/Changelog/2.4.6/onDropContent.md --- # 接收外部拖拽内容 `onDropContent` 是 Scripting 提供的一个视图修饰符,用于将当前视图设置为**拖放目标(Drop Target)**,以接收从其他 App 拖拽进入的文件、图片或文本内容。 *** ## 功能说明 通过 `onDropContent`,你可以实现以下能力: - 接收来自其他 App 的拖拽内容 - 使用 UTType 精确限制可接收的数据类型 - 实时感知拖拽指针是否悬停在视图上方 - 在内容被放下时,通过 `ItemProvider` 启动数据加载流程 - 对安全作用域文件建立持久访问权限 *** ## 修饰符定义 ```ts onDropContent?: { types: UTType[] isTarget: { value: boolean onChanged: (value: boolean) => void } | Observable perform: (attachments: ItemProvider[]) => boolean } ``` *** ## 参数说明 ### types 用于指定当前视图**可以接收的内容类型列表**,类型值为 UTType 字符串。 当拖拽内容不包含任意匹配的类型时: - 当前视图不会激活为放置目标 - `isTarget` 不会发生变化 - `perform` 不会被调用 示例: ```ts types: ["public.image", "public.movie"] ``` *** ### isTarget 用于表示拖拽操作是否悬停在当前视图上方。 - 当拖拽进入视图区域时,值为 `true` - 当拖拽移出视图区域时,值为 `false` 支持以下两种形式: - 绑定对象形式 ```ts { value: boolean onChanged: (value: boolean) => void } ``` - Observable 形式 ```ts Observable ``` Observable 形式适合与 `useObservable` 搭配使用,语义更简洁。 *** ### perform 当符合 `types` 要求的内容被成功放下时触发。 ```ts perform: (attachments: ItemProvider[]) => boolean ``` - 参数 `attachments` 为 `ItemProvider` 数组 - 每一个 `ItemProvider` 表示一个被拖入的内容项 - 函数返回值表示是否成功处理了此次拖放操作 返回值说明: - 返回 `true` 表示拖放被成功接收 - 返回 `false` 表示未处理该拖放内容 *** ## perform 的执行规则(重要) 在 `perform` 中需要遵循以下规则: - 必须在 `perform` 函数的同步执行过程中**启动对 ItemProvider 的加载** - 允许使用 `Promise` / `then` 等方式延迟完成加载 - 不允许在 `perform` 返回之后,再通过其他回调或事件启动加载 - 返回 `false` 时,系统会认为该拖放未被接受 原因说明: - 拖放内容受系统安全机制保护 - 只有在 `perform` 执行期间,脚本才拥有对拖放数据的访问权限 - 若未在此期间启动加载,后续将无法访问对应资源 *** ## ItemProvider 的使用方式 在 `perform` 中,开发者应当通过 `ItemProvider` 判断类型并启动加载。 常见流程包括: - 使用 `hasItemConforming` 判断内容类型 - 根据内容类型选择合适的加载方式 - 对文件类资源获取路径并进行后续处理 *** ## 示例用法 ```tsx const isTarget = useObservable(false) return { const images: UIImage[] = [] const videos: string[] = [] let found = false for (const attachment of attachments) { if (attachment.hasItemConforming("public.png")) { found = true attachment.loadUIImage().then(image => { if (image != null) { images.push(image) } }) } else if (attachment.hasItemConforming("public.movie")) { found = true attachment.loadFilePath("public.movie").then(filePath => { if (filePath != null) { // 为安全作用域文件创建书签 FileManager.addFileBookmark(filePath) videos.push(filePath) } }) } } return found } }} > ... ``` *** ## 安全作用域文件访问 通过 `onDropContent` 获取的文件路径,通常属于**安全作用域资源**。 这类路径在以下情况下可能失效: - `perform` 返回之后 - App 重启 - 脚本生命周期结束 为保证后续仍可访问文件,建议在获取路径后创建文件书签。 *** ## FileManager.addFileBookmark ```ts FileManager.addFileBookmark(path: string, name?: string): string | null ``` 说明: - 为指定文件或文件夹创建安全作用域书签 - 适用于通过 `Photos`、`onDropContent` 等 API 获取的路径 - 返回书签名称,用于后续访问或移除 示例: ```ts const bookmarkName = FileManager.addFileBookmark(filePath) ``` *** ## FileManager.removeFileBookmark ```ts FileManager.removeFileBookmark(name: string): boolean ``` 说明: - 移除指定名称的文件书签 - 当不再需要访问对应文件时应及时调用 - 返回是否成功移除 示例: ```ts FileManager.removeFileBookmark(bookmarkName) ``` *** ## 使用建议 - 在 `types` 中尽量明确声明可接收的内容类型 - 在 `perform` 中只负责启动加载,不要等待加载完成 - 对图片等轻量内容可直接加载为对象 - 对视频、音频、文档等资源优先使用文件路径 - 对需要长期访问的文件务必创建书签 - 在资源不再使用时移除对应书签 --- url: /doc_v2/zh/guide/Control Widget.md --- # 控制中心的小组件 Scripting 支持用户在控制中心或锁屏界面添加按钮(Button)或开关(Toggle)控件,并通过绑定脚本 `AppIntent` 实现自定义逻辑。控件支持状态反馈、图标动态切换、隐私显示控制等能力。 *** ## 控件标签类型定义 ### `ControlWidgetLabel` 表示控件主标签或状态标签的信息结构。 | 字段名 | 类型 | 描述 | | ------------------ | ---------- | -------------------- | | `title` | `string` | 标签的文本标题。 | | `systemImage` | `string?` | 可选的 SF Symbols 图标名称。 | | `privacySensitive` | `boolean?` | 控件在设备锁定时是否隐藏该标签内容。 | *** ## 一、按钮控件:`ControlWidgetButton` 用于添加一个点击后触发指定意图的按钮控件。 ```ts function ControlWidgetButton(props: ControlWidgetButtonProps): JSX.Element ``` ### `ControlWidgetButtonProps` | 字段名 | 类型 | 描述 | | -------------------- | ----------------------------- | ---------------------------------------------------- | | `privacySensitive` | `boolean?` | 控件是否在锁屏状态下隐藏其内容与状态。 | | `intent` | `AppIntent` | 点击按钮后触发的意图(AppIntent 实例)。 | | `label` | `ControlWidgetLabel` | 按钮主标签,显示标题与图标。 | | `activeValueLabel` | `ControlWidgetLabel \| null?` | 按钮激活(Active)状态时显示的标签。设置后需同时提供 `inactiveValueLabel`。 | | `inactiveValueLabel` | `ControlWidgetLabel \| null?` | 按钮非激活(Inactive)状态时显示的标签。设置后需同时提供 `activeValueLabel`。 | > 若提供了 `activeValueLabel` 或 `inactiveValueLabel`,建议同时提供两者,以确保状态一致性。此类状态标签的图标会覆盖 `label` 中的图标。 *** ## 二、开关控件:`ControlWidgetToggle` 用于添加一个可切换状态的开关控件,自动将布尔值通过绑定意图传入。 ```ts function ControlWidgetToggle(props: ControlWidgetToggleProps): JSX.Element ``` ### `ControlWidgetToggleProps` | 字段名 | 类型 | 描述 | | -------------------- | ----------------------------- | --------------------------------------------- | | `privacySensitive` | `boolean?` | 控件是否在锁屏状态下隐藏其内容与状态。 | | `intent` | `AppIntent` | 控件状态切换时触发的意图。泛型参数 `T` 必须包含 `value: boolean`。 | | `label` | `ControlWidgetLabel` | 控件主标签。 | | `activeValueLabel` | `ControlWidgetLabel \| null?` | 控件激活(开)状态时显示的标签。需与 `inactiveValueLabel` 搭配使用。 | | `inactiveValueLabel` | `ControlWidgetLabel \| null?` | 控件非激活(关)状态时显示的标签。需与 `activeValueLabel` 搭配使用。 | *** ## 三、ControlWidget 命名空间 ```ts namespace ControlWidget ``` ### `ControlWidget.parameter: string` 用户在控件配置界面中设置的参数值,通常用于标识目标对象,如设备 ID、门编号等。 *** ### `ControlWidget.present(element: VirtualNode): void` 设置控件的显示内容。仅允许传入 `ControlWidgetButton` 或 `ControlWidgetToggle` 元素。 #### 注意: - 若使用 `control_widget_button.tsx`,只能呈现 `ControlWidgetButton`; - 若使用 `control_widget_toggle.tsx`,只能呈现 `ControlWidgetToggle`; - 若控件需要在锁屏隐藏,可在顶层组件上设置 `privacySensitive`; - 如果仅需要隐藏标签或状态信息,可在相应的 `ControlWidgetLabel` 中设置 `privacySensitive`。 #### 示例: ```tsx /// app_intents.tsx export const ToggleDoorIntent = AppIntentManager.register({ name: "ToggleDoorIntent", protocol: AppIntentProtocol.AppIntent, perform: async ({ id, value }: { id: string; value: boolean }) => { await setDoorState(id, value) ControlWidget.reloadToggles() } }) /// control_widget_toggle.tsx async function run() { const doorId = ControlWidget.parameter || "default" const data = await fetchDoorData(doorId) ControlWidget.present( ) } run() ``` *** ### `ControlWidget.reloadButtons(): void` 重新加载所有按钮控件。用于意图执行后刷新状态显示。 *** ### `ControlWidget.reloadToggles(): void` 重新加载所有切换控件。常用于状态变更后触发 UI 更新。 *** ## 四、开发建议 1. 所有控件必须绑定一个 `AppIntent`,用于定义交互逻辑。 2. 切换(Toggle)控件的参数必须包含 `{ value: boolean }`,可使用`AppIntentProtocol.AppIntent`协议,内部会强制切换为 `SetValueIntent` 协议。 3. 若为控件提供状态标签,建议提供完整的 `activeValueLabel` 与 `inactiveValueLabel` 配对,以提升可读性。 4. 图标使用 SF Symbols 命名的系统图标。 5. 在意图执行中变更控件状态时,应调用 `ControlWidget.reloadButtons()` 或 `reloadToggles()` 以触发前端刷新。 --- url: /doc_v2/zh/guide/Custom Keyboard.md --- # 自定义键盘 `CustomKeyboard` 是 Scripting 提供的全局命名空间,用于开发 iOS 自定义键盘扩展。在 `keyboard.tsx` 脚本中使用该 API,可以渲染自定义键盘 UI,并访问当前输入状态、插入文本、控制光标、监听输入事件、调整高度,并在多个脚本之间导航切换。 ## 一、使用环境与前提 ### 环境要求 - 必须在脚本项目中创建名为 **`keyboard.tsx`** 的文件; - 所有 `CustomKeyboard` 方法 **仅可在键盘扩展环境中使用**; - 在 **App 脚本、Intent (`intent.tsx`)、小组件 (`widget.tsx`) 中无法使用此 API**; - 系统设置路径: ``` 设置 > 通用 > 键盘 > 键盘 > 添加新键盘 > 选择 Scripting ``` 添加后,进入 Scripting 键盘详情页,开启 **允许完全访问**,以启用网络请求、剪贴板访问等高级功能。 *** ## 二、展示键盘 UI ### `present(node: VirtualNode): void` 用于展示自定义键盘界面。必须在 `keyboard.tsx` 中调用一次。 ```tsx function MyKeyboard() { return 你好,世界 } CustomKeyboard.present() ``` *** ## 三、输入状态查询 | 属性名 | 类型 | 说明 | | ------------------ | ------------------------- | ----------- | | `textBeforeCursor` | `Promise` | 光标前的文本 | | `textAfterCursor` | `Promise` | 光标后的文本 | | `selectedText` | `Promise` | 当前选中的文本(如有) | | `hasText` | `Promise` | 输入框是否包含文本内容 | *** ## 四、输入特征(traits) ### `useTraits(): TextInputTraits` 获取当前输入框的系统特征(如键盘类型、返回键样式等)。值在 `textDidChange` 和 `selectionDidChange` 事件中自动更新。 ### `traits: TextInputTraits` 为静态快照,不会自动更新。建议在组件中使用 `useTraits()`。 常见字段包括: - `keyboardType`:如 `'default'`, `'emailAddress'`, `'numberPad'` - `returnKeyType`:如 `'done'`, `'go'`, `'search'` - `textContentType`:如 `'username'`, `'password'`, `'oneTimeCode'` - `keyboardAppearance`:`'light'`, `'dark'` 等 *** ## 五、文本操作 ### `insertText(text: string): Promise` 在光标处插入文本。 ### `deleteBackward(): Promise` 删除光标前的一个字符。 ### `moveCursor(offset: number): Promise` 移动光标位置。负数为向左,正数为向右。 ### `setMarkedText(text, location, length): Promise` 设置标记文本(用于拼音输入等组合输入)。 ### `unmarkText(): Promise` 取消当前标记文本。 *** ## 六、键盘行为控制 ### `dismiss(): Promise` 关闭键盘。 ### `nextKeyboard(): Promise` 切换至系统中的下一个键盘。 ### `requestHeight(height: number): Promise` 请求调整键盘高度(单位为 pt)。推荐范围为 **216\~360pt**,超出范围可能被系统忽略。 ### `setHasDictationKey(value: boolean): Promise` 设置是否显示语音输入按钮(麦克风图标)。 ### `setToolbarVisible(visible: boolean): Promise` 控制顶部工具栏的显示/隐藏。默认显示,适用于调试等场景。 *** ## 七、导航控制 ### `dismissToHome(): Promise` 关闭当前键盘脚本,返回 Scripting 键盘首页(脚本列表)。适用于用户在多个脚本之间自由切换的场景。 ```ts await CustomKeyboard.dismissToHome() ``` *** ## 八、用户反馈 ### `playInputClick(): void` 播放标准键盘按键音,建议在模拟按键操作时调用,提升交互体验。 ```ts CustomKeyboard.playInputClick() ``` *** ## 九、事件监听 ### `addListener(event, callback): void` 注册事件监听器: | 事件名 | 回调参数 | 说明 | | --------------------- | ----------------------------------- | ------ | | `textWillChange` | `() => void` | 文本将要变更 | | `textDidChange` | `(traits: TextInputTraits) => void` | 文本已变更 | | `selectionWillChange` | `() => void` | 光标将变更 | | `selectionDidChange` | `(traits: TextInputTraits) => void` | 光标已变更 | ### `removeListener(event, callback): void` 移除指定监听器。 ### `removeAllListeners(event): void` 移除指定事件的所有监听器。 *** ## 十、完整示例 ```tsx function MyKeyboard() { const traits = CustomKeyboard.useTraits() const insert = async (text: string) => { CustomKeyboard.playInputClick() await CustomKeyboard.insertText(text) } return ( 输入类型:{traits.keyboardType} `) await webView.present({ navigationTitle: '网页视图示例' }) webView.dispose() ``` --- url: /doc_v2/zh/guide/Intent/Intent.continueInForeground.md --- # Intent.continueInForeground `Intent.continueInForeground` 用于在脚本从 Shortcuts 中后台执行时,**请求系统将流程转移到 Scripting App 的前台继续运行**。 此过程需要用户明确确认。 适用场景包括: - 需要展示完整 UI(如表单、列表、导航页面) - 需要用户在 App 内进行交互操作 - 后续步骤无法在后台执行 调用此方法后,系统会弹出确认对话框: - 用户 **允许** → Scripting App 打开到前台,脚本继续执行 - 用户 **取消** → 当前脚本立即终止 - 此行为完全由系统管理,开发者无需手动处理跳转流程 由于该能力基于 iOS 26 引入的 AppIntents 行为: **该 API 只能在 iOS 26 及以上系统使用。** *** \##API 定义 ```ts function continueInForeground( dialog?: Dialog | null, options?: { alwaysConfirm?: boolean; }, ): Promise; ``` *** \##参数说明 ## dialog?: Dialog | null 用于提示用户为什么需要切换到前台继续执行。 `Dialog` 的类型格式支持三种形式: ```ts type Dialog = | string | { full: string; supporting: string } | { full: string; supporting: string; systemImageName: string } | { full: string; systemImageName: string }; ``` 示例: ```ts "是否前往应用继续执行?"; ``` 或带辅助说明: ```ts { full: "需要在应用中继续执行下一步操作", supporting: "接下来的步骤需要完整的 UI 交互。", systemImageName: "app" } ``` 若传入 `null`,系统可能不显示提示,仅直接触发系统确认(不推荐)。 *** ## `options?: { alwaysConfirm?: boolean }` 用于控制系统是否每次都显示确认提示。 - `alwaysConfirm: false`(默认) 系统一般会根据上下文自动判断是否需要确认。 - `alwaysConfirm: true` 每次调用都会提示用户明确确认。 示例: ```ts { alwaysConfirm: true; } ``` *** \##执行流程 执行 `await Intent.continueInForeground(...)` 时: 1. 快捷指令执行暂停 2. 系统弹出确认对话框 3. 用户选择: - **确认** → 打开 Scripting App → 脚本继续 - **取消** → 脚本立即终止 4. 后续脚本在 Scripting App 前台环境中继续执行 **注意:脚本不会在后台继续运行,必须等待用户操作。** *** \##典型应用场景 推荐在以下场景调用: - 需要展示完整的导航界面或交互表单(如示例中的 TextField) - 需要使用 `Navigation.present` 呈现 UI - 需要 App 内操作如: - 预览文件 - 编辑长文本 - 选择复杂数据 - 多步骤流程 不推荐在以下情况使用: - 单纯的数据处理,不需要 UI - 简单操作已经可通过 SnippetIntent 完成 *** \##完整示例代码 以下示例展示如何从 Shortcuts 通过 `continueInForeground` 切换到 Scripting App 前台,然后展示 UI 让用户输入文本,输入结束后再返回 Shortcuts。 ```tsx // intent.tsx import { Button, Intent, List, Navigation, NavigationStack, Script, Section, TextField, useState, } from "scripting"; function View() { const dismiss = Navigation.useDismiss(); const [text, setText] = useState(""); return (

); } async function runIntent() { // 请求系统将执行流程切换到 Scripting App 前台 await Intent.continueInForeground("Do you want to open the app and continue?"); // 在前台呈现交互式 UI,用户填写文本 const text = await Navigation.present(); // 可选:返回到快捷指令界面 Safari.openURL("shortcuts://"); // 返回结果给 Shortcuts Script.exit(Intent.text(text ?? "No text return")); } runIntent(); ``` *** \##注意事项与最佳实践 - **必须运行在 iOS 26+**,否则会抛出异常或行为不可用。 - 若脚本依赖用户输入、复杂 UI 或操作,请使用该 API 触发前台模式。 - 对话内容应清晰说明需要用户切换前台的原因,提升用户信任度。 - 若用户拒绝,脚本将终止,开发者无需自行处理取消逻辑。 - 可以与 SnippetIntent 结合,构建完整的后台 UI + 前台 UI 混合流程。 --- url: /doc_v2/zh/guide/Intent/Intent.requestConfirmation.md --- # Intent.requestConfirmation `Intent.requestConfirmation` 用于在脚本执行过程中,**向用户请求确认某项操作**。 调用后,系统会暂停脚本执行,并展示一个基于 **SnippetIntent 的 UI** 作为确认界面,同时可显示提示对话内容。 确认流程行为: - 用户 **确认** → Promise resolve,脚本继续执行 - 用户 **取消** → 当前脚本终止执行 - 确认界面通过传入的 **SnippetIntent** 的 UI 定义 - 系统自动管理此流程,无需开发者处理 UI 呈现逻辑 **该 API 仅可在 iOS 26 及以上系统使用。** *** \##API 定义 ```ts function requestConfirmation( actionName: ConfirmationActionName, snippetIntent: AppIntent, options?: { dialog?: Dialog; showDialogAsPrompt?: boolean; }, ): Promise; ``` *** \##参数说明 ## actionName: ConfirmationActionName 用于告诉系统“要确认的行为语义是什么”,系统会根据该值生成自然语言文案。例如: - `"set"` → “确定要设置…?” - `"buy"` → “确定要购买…?” - `"toggle"` → “是否切换…?” 可选值如下(与苹果 AppIntents 框架一致): ``` "add" | "addData" | "book" | "buy" | "call" | "checkIn" | "continue" | "create" | "do" | "download" | "filter" | "find" | "get" | "go" | "log" | "open" | "order" | "pay" | "play" | "playSound" | "post" | "request" | "run" | "search" | "send" | "set" | "share" | "start" | "startNavigation" | "toggle" | "turnOff" | "turnOn" | "view" ``` 选择合适的语义有助于提高确认界面的自然体验。 *** ## snippetIntent: SnippetIntent 必须是一个 **注册为 SnippetIntent 类型的 AppIntent**: ```ts AppIntent; ``` 用户在确认界面中看到的内容就是该 SnippetIntent 的 `perform()` 返回的 UI,例如选项列表、内容预览等。 *** ## `options?: { dialog?: Dialog; showDialogAsPrompt?: boolean }` ### dialog?: Dialog 用于在确认 UI 上方或系统对话框中显示提示文本。 支持四种格式: ```ts type Dialog = | string | { full: string; supporting: string } | { full: string; supporting: string; systemImageName: string } | { full: string; systemImageName: string }; ``` 示例: ```ts "是否继续?"; ``` 更复杂的: ```ts { full: "确定要设置此颜色吗?", supporting: "此操作将更新应用的主题颜色。", systemImageName: "paintpalette" } ``` 用途: - 解释确认动作含义 - 提醒用户可能产生的影响 - 提供更友好的交互上下文 *** ### showDialogAsPrompt?: boolean 默认值:`true` 决定系统是否以「提示弹窗」方式显示对话文本。 设为 `false` 时,文本可能以更沉浸的方式显示在 Snippet 卡片内部。 *** \##执行流程 调用 `await Intent.requestConfirmation(...)` 时脚本执行顺序如下: 1. 脚本暂停执行 2. 系统展示确认界面(SnippetIntent UI + 可选 dialog 文案) 3. 用户进行交互: - **确认** → Promise resolve,脚本继续 - **取消** → 脚本终止执行 4. 不需要开发者手动关闭 UI 此流程完全由系统管理。 *** \##使用场景 以下场景推荐使用 `requestConfirmation`: - 修改重要设置(如主题颜色、隐私设置) - 对数据执行有副作用的操作(如删除、更新、重置) - 流程中一步需用户明确授权 - 启动某个需要用户选择的 UI 子流程(如颜色选择器、账号切换器) 不适用场景: - 简单数据处理,不需要用户确认 - 可以在后台无 UI 完成的操作 *** \##完整示例代码 以下示例展示如何使用 `requestConfirmation` 请求用户确认一次颜色选择,并在确认后继续执行脚本。 假设你已有两个 SnippetIntent: - `PickColorIntent`:颜色选择 UI - `ShowResultIntent`:结果展示 UI ## intent.tsx 示例 ```tsx import { Intent, Script } from "scripting"; import { PickColorIntent, ShowResultIntent } from "./app_intents"; async function runIntent() { // 第一步:请求用户确认颜色选择 await Intent.requestConfirmation("set", PickColorIntent(), { dialog: { full: "确定要设置此颜色吗?", supporting: "此操作将更新应用的主题颜色。", systemImageName: "paintpalette", }, }); // 第二步:读取来自 Shortcuts 的输入(如果有) const text = Intent.shortcutParameter?.type === "text" ? Intent.shortcutParameter.value : "No text parameter from Shortcuts"; // 第三步:呈现最终 SnippetIntent const snippet = Intent.snippetIntent({ snippetIntent: ShowResultIntent({ content: text }), }); Script.exit(snippet); } runIntent(); ``` *** \##注意事项与最佳实践 - **必须运行在 iOS 26+** 提前检查系统版本或优雅降级。 - **总是提供清晰的 dialog 文案** 确认行为应让用户理解,不应仅依赖 Snippet UI 本身。 - **用于重要或可逆性较差的操作** 如修改设置、启动后台任务、提交数据等。 - **与 SnippetIntent 配合使用效果最佳** 因为确认 UI 直接展示 SnippetIntent 的视图。 - **用户取消时脚本会被系统直接终止** 不要在后续代码中假设脚本一定会继续执行。 --- url: /doc_v2/zh/guide/Intent/Quick Start.md --- # 快速开始 Scripting 支持通过 `intent.tsx` 文件创建 iOS Intents,实现脚本与系统分享扩展(Share Sheet)和快捷指令(Shortcuts)的深度集成。你可以接收来自用户的文本、图片、文件和 URL 等输入,并返回结果供调用方使用。通过 UI 展示、数据处理与结果返回,可构建灵活且强大的自动化流程。 *** ## 一、创建和配置 Intent ### 1. 创建 Intent 脚本 1. 在 Scripting 中新建一个脚本项目。 2. 添加名为 `intent.tsx` 的文件,并编写处理逻辑和可选的 UI 组件。 ### 2. 配置支持的输入类型 点击编辑器顶部标题栏中的项目名称,打开 **Intent 设置页面**,选择该脚本支持的输入类型,如: - 文本(Text) - 图片(Image) - 文件路径(File URL) - URL 配置后,该脚本就能在分享扩展或 Shortcuts 中处理相应类型的数据。 *** ## 二、处理输入数据 在 `intent.tsx` 中,可通过以下 API 访问用户传入的数据: | 属性名 | 说明 | | -------------------------- | ---------------------------------------- | | `Intent.shortcutParameter` | Shortcuts 中传入的单个参数,包含 `.type` 和 `.value` | | `Intent.textsParameter` | 文本字符串数组 | | `Intent.urlsParameter` | URL 字符串数组 | | `Intent.imagesParameter` | 图片数组(UIImage 实例) | | `Intent.fileURLsParameter` | 文件路径数组(本地 URL) | 示例: ```ts if (Intent.shortcutParameter) { if (Intent.shortcutParameter.type === "text") { console.log(Intent.shortcutParameter.value) } } ``` *** ## 三、返回结果 使用 `Script.exit(result)` 结束脚本执行并返回结果给调用方,例如 Shortcuts 或另一个脚本。支持的返回类型包括: - 文本:`Intent.text(value)` - 富文本:`Intent.attributedText(value)` - URL:`Intent.url(value)` - JSON 数据:`Intent.json(value)` - 文件路径:`Intent.file(value)` 或 `Intent.fileURL(value)` 示例: ```ts import { Script, Intent } from "scripting" Script.exit(Intent.text("处理完成")) ``` *** ## 四、展示交互式 UI 你可以使用 `Navigation.present()` 呈现一个自定义界面,展示输入信息或收集用户反馈。在 UI 交互结束后调用 `Script.exit()` 返回结果。 示例: ```ts import { Intent, Script, Navigation, VStack, Text } from "scripting" function MyIntentView() { return ( {Intent.textsParameter?.[0]} ) } async function run() { await Navigation.present() Script.exit() } run() ``` *** ## 五、在分享扩展中使用 当脚本项目启用了对应类型的输入支持,Scripting 会自动集成到系统分享菜单: 1. 用户选中内容(如 Safari 中的文字或图片),点击分享按钮。 2. 分享列表中选择 **Scripting**。 3. 显示支持当前输入类型的脚本列表,供用户执行。 *** ## 六、与 Shortcuts 集成 你可以在 Shortcuts 应用中调用 Scripting 脚本: - **运行脚本(Run Script)**:后台执行,无 UI。 - **在 App 中运行脚本(Run Script in App)**:前台执行,支持 UI 展示。 操作步骤: 1. 在 Shortcuts 中添加 “Run Script” 或 “Run Script in App” 操作。 2. 选择目标脚本。 3. 配置参数,执行脚本。 *** ## 七、Intent API 参考 ### `Intent` 类属性 | 属性 | 类型 | 说明 | | ------------------- | ------------------- | ------------------------------------- | | `shortcutParameter` | `ShortcutParameter` | Shortcuts 传入的参数对象,包含 `type` 和 `value` | | `textsParameter` | `string[]` | 文本输入数组 | | `urlsParameter` | `string[]` | URL 字符串数组 | | `imagesParameter` | `UIImage[]` | 图片数组(路径或图片对象) | | `fileURLsParameter` | `string[]` | 文件路径数组(本地 URL) | ### `Intent` 类方法 | 方法 | 返回类型 | 示例 | | ------------------------------ | --------------------------- | ------------------------------------- | | `Intent.text(value)` | `IntentTextValue` | `Intent.text("内容")` | | `Intent.attributedText(value)` | `IntentAttributedTextValue` | `Intent.attributedText("富文本")` | | `Intent.url(value)` | `IntentURLValue` | `Intent.url("https://example.com")` | | `Intent.json(value)` | `IntentJsonValue` | `Intent.json({ key: "value" })` | | `Intent.file(filePath)` | `IntentFileValue` | `Intent.file("/path/to/file.txt")` | | `Intent.fileURL(filePath)` | `IntentFileURLValue` | `Intent.fileURL("/path/to/file.pdf")` | | `Intent.image(UIImage)` | `IntentImageValue` | `Intent.image(uiImage)` | *** ## 八、最佳实践与注意事项 - 所有脚本应显式调用 `Script.exit()` 以确保内存安全。 - 推荐在 UI 脚本中使用 `await Navigation.present()` 之后再调用 `Script.exit()`。 - 对于大文件或图像,建议使用 “Run Script in App” 模式,以避免系统内存限制导致的崩溃。 - 如果脚本需要共享数据,可通过 URL Scheme 或 `queryParameters` 实现。 --- url: /doc_v2/zh/guide/Intent/SnippetIntent.md --- # SnippetIntent SnippetIntent 是一种特殊类型的 AppIntent,可在 Shortcuts 中生成原生的 Snippet UI 卡片。它适用于: - 多步骤表单式交互 - 从 Shortcuts 中获取用户输入 - 键值选择、确认、展示结果等轻量级交互 - 在 Shortcuts 工作流中内嵌 UI 组件 SnippetIntent 特点如下: 1. 在 Scripting 中必须通过 `AppIntentManager.register` 注册 2. `protocol` 必须为 `AppIntentProtocol.SnippetIntent` 3. `perform()` 必须返回一个 `VirtualNode`(TSX UI) 4. 在脚本中必须以 `Intent.snippetIntent()` 封装后返回 5. Shortcuts 必须使用「Show Snippet Intent」动作才能显示 Snippet UI *** \##系统要求 **SnippetIntent 只能在 iOS 26 及以上系统运行。** 在 iOS 26 以下环境: - 无法调用 `Intent.snippetIntent` - 无法使用 `Intent.requestConfirmation` - Shortcuts 中不存在「Show Snippet Intent」动作 - SnippetIntent 类型的 AppIntent 不会被 Shortcuts 正常识别 *** \##注册 SnippetIntent(app\_intents.tsx) 在 `app_intents.tsx` 中声明 SnippetIntent: ```tsx export const PickColorIntent = AppIntentManager.register({ name: "PickColorIntent", protocol: AppIntentProtocol.SnippetIntent, perform: async () => { return ; }, }); ``` 再例如: ```tsx export const ShowResultIntent = AppIntentManager.register({ name: "ShowResultIntent", protocol: AppIntentProtocol.SnippetIntent, perform: async ({ content }: { content: string }) => { return ; }, }); ``` 要求: - `protocol` 必须为 `AppIntentProtocol.SnippetIntent` - `perform()` 必须返回 `VirtualNode` - 与普通 AppIntent 区别在于返回的是 UI,而非数据 *** \##SnippetIntent 返回值封装:Intent.snippetIntent SnippetIntent 不能直接作为 JS 返回值,必须通过 `Intent.snippetIntent()` 包装成 `IntentSnippetIntentValue`。 ```tsx const snippetValue = Intent.snippetIntent({ value: Intent.text("Some value returning for Shortcuts"), snippetIntent: ShowResultIntent({ content: "Example Text", }), }); Script.exit(snippetValue); ``` ### 类型定义 ```ts type SnippetIntentValue = { value?: | IntentAttributedTextValue | IntentFileURLValue | IntentJsonValue | IntentTextValue | IntentURLValue | IntentFileValue | null; snippetIntent: AppIntent; }; declare class IntentSnippetIntentValue extends IntentValue<"SnippetIntent", SnippetIntentValue> { value: SnippetIntentValue; type: "SnippetIntent"; } ``` 封装的返回值可被 Shortcuts 的「Show Snippet Intent」动作识别并展示 UI。 *** \##Snippet 确认界面:Intent.requestConfirmation SnippetIntent 支持在执行逻辑中先请求用户确认某个操作。此能力同样基于 iOS 26。 ```ts Intent.requestConfirmation( actionName: ConfirmationActionName, intent: AppIntent, options?: { dialog?: Dialog; showDialogAsPrompt?: boolean; } ): Promise ``` ### ConfirmationActionName 这些名称会影响 Shortcuts UI 中呈现的文案,例如 “Set …”、“Add …”、“Toggle …” 等。 示例值: ``` "add" | "addData" | "book" | "buy" | "call" | "checkIn" | "continue" | "create" | "do" | "download" | "filter" | "find" | "get" | "go" | "log" | "open" | "order" | "pay" | "play" | "playSound" | "post" | "request" | "run" | "search" | "send" | "set" | "share" | "start" | "startNavigation" | "toggle" | "turnOff" | "turnOn" | "view" ``` ### 示例 ```tsx await Intent.requestConfirmation("set", PickColorIntent()); ``` 效果: - Shortcuts 弹出 PickColorIntent 对应的 Snippet UI - 用户点击确认后 Promise resolve - 用户取消时脚本执行终止 *** \##Shortcuts 的「Show Snippet Intent」动作(iOS 26+) Shortcuts 在 iOS 26 新增动作: **Show Snippet Intent** 用于展示 SnippetIntent 返回的 Snippet UI。 ### 与其他动作对比 | Shortcuts 动作 | 显示界面 | 支持 SnippetIntent | 场景 | | ---------------------------- | -------------------- | ---------------- | ---------------- | | Run Script | 无 UI | 否 | 纯数据处理 | | Run Script in App | Scripting App UI(前台) | 否 | 大型 UI、文件选择等 | | Show Snippet Intent(iOS 26+) | Snippet 卡片 UI | 是 | SnippetIntent 场景 | 使用方式: 1. 在 Shortcuts 中添加「Show Snippet Intent」 2. 选择脚本项目(需包含 intent.tsx) 3. 脚本返回 `Intent.snippetIntent(...)` 4. Shortcuts 显示 Snippet UI *** \##IntentMemoryStorage — 跨 AppIntent 状态共享 ## 1. 为什么需要 IntentMemoryStorage 由于系统行为,每次 Intent 执行后: - AppIntent 的 `perform()` 执行完毕后立即销毁上下文 - `intent.tsx` 执行完并调用 `Script.exit()` 后脚本上下文也会完全释放 因此无法依赖 JS 变量在多个 Intent 之间保持状态。 例如: - PickColorIntent(选择颜色) - SetColorIntent(设置颜色) - ShowResultIntent(展示颜色结果) 在这些 Intent 之间共享状态必须依赖持久化存储。 ## 2. IntentMemoryStorage 提供轻量级、跨 Intent 的共享存储 API 定义: ```ts namespace IntentMemoryStorage { function get(key: string): T | null; function set(key: string, value: any): void; function remove(key: string): void; function contains(key: string): boolean; function clear(): void; function keys(): string[]; } ``` 用途: - 存储小量状态,例如当前颜色、当前步骤、临时选项 - 在多个 AppIntent 之间共享数据 - 生命周期跨 Intent 调用,但随脚本生命周期管理 ### 示例:存储用户颜色选择 ```ts IntentMemoryStorage.set("color", "systemBlue"); const color = IntentMemoryStorage.get("color"); ``` ### 建议 - 不要存储大型数据(如大图像、长文本) - 大型数据请使用: - `Storage`(持久键值存储) - `FileManager` 写入 appGroupDocumentsDirectory IntentMemoryStorage 适合作为临时状态共享,不适合当作数据库使用。 *** \##完整示例(iOS 26+) ## app\_intents.tsx ```tsx export const SetColorIntent = AppIntentManager.register({ name: "SetColorIntent", protocol: AppIntentProtocol.AppIntent, perform: async (color: Color) => { IntentMemoryStorage.set("color", color); }, }); export const PickColorIntent = AppIntentManager.register({ name: "PickColorIntent", protocol: AppIntentProtocol.SnippetIntent, perform: async () => { return ; }, }); export const ShowResultIntent = AppIntentManager.register({ name: "ShowResultIntent", protocol: AppIntentProtocol.SnippetIntent, perform: async ({ content }: { content: string }) => { const color = IntentMemoryStorage.get("color") ?? "systemBlue"; return ; }, }); ``` ## intent.tsx ```tsx async function runIntent() { // 1. 通过 Snippet 请求用户确认颜色 await Intent.requestConfirmation("set", PickColorIntent()); // 2. 从 Shortcuts 输入中读取文本 const textContent = Intent.shortcutParameter?.type === "text" ? Intent.shortcutParameter.value : "No text parameter from Shortcuts"; // 3. 创建 SnippetIntent 返回结果 const snippetIntentValue = Intent.snippetIntent({ snippetIntent: ShowResultIntent({ content: textContent }), }); Script.exit(snippetIntentValue); } runIntent(); ``` --- url: /doc_v2/zh/guide/Interactive Widget and LiveActivity.md --- # 可互动的小组件和灵动岛 **Scripting** 应用支持在 **小组件** 和 **LiveActivity(灵动岛)** 中添加互动的功能,使您可以通过 `Button` 和 `Toggle` 组件创建动态、交互式的 UI。这些控件可以执行 **AppIntent** 来触发操作,从而增强小组件和 LiveActivity 的功能。 *** ## 1. AppIntent 简介 ### 什么是 AppIntent? **AppIntent** 定义了一个由控件(如 `Button` 或 `Toggle`)触发的特定操作,用于小组件或 LiveActivity UI。AppIntent 将 UI 组件与可执行逻辑连接起来,实现无缝交互。 ### 支持的协议 AppIntent 可以实现以下协议: - **`AppIntent`**:通用意图,用于触发自定义操作。 - **`AudioPlaybackIntent`**:处理音频播放(如播放、暂停或切换音频状态)。 - **`AudioRecordingIntent`**:管理音频录制状态(需要 iOS 18+,并且在录制期间保持 LiveActivity 活跃)。 - **`LiveActivityIntent`**:修改或管理 LiveActivity 状态。 *** ## 2. 注册 AppIntent 在使用 **AppIntent** 之前,必须通过 `AppIntentManager.register` 方法在 `app_intents.tsx` 文件中注册。 ### 示例:注册 AppIntent ```typescript // app_intents.tsx import { AppIntentManager, AppIntentProtocol } from "scripting" // 注册不带参数的 AppIntent const IntentWithoutParams = AppIntentManager.register({ name: "IntentWithoutParams", protocol: AppIntentProtocol.AppIntent, perform: async (params: undefined) => { // 执行自定义操作 console.log("Intent 被触发") // 可选:刷新小组件 Widget.reloadAll() } }) // 注册带参数的 AppIntent const ToggleIntentWithParams = AppIntentManager.register({ name: "ToggleIntentWithParams", protocol: AppIntentProtocol.AudioPlaybackIntent, perform: async (audioName: string) => { // 根据参数执行操作 console.log(`切换音频播放状态:${audioName}`) Widget.reloadAll() } }) ``` *** ## 3. 在小组件或 LiveActivity UI 中使用 AppIntent 注册完 AppIntent 后,可以在 `widget.tsx` 或 LiveActivity UI 文件中的 `Button` 和 `Toggle` 等交互组件中链接这些 AppIntent。 ### 示例:在小组件中使用 AppIntent ```typescript // widget.tsx import { VStack, Button, Toggle } from "scripting" import { IntentWithoutParams, ToggleIntentWithParams } from "./app_intents" import { model } from "./model" function WidgetView() { return ( ] }} trailingSwipeActions={{ actions: [ , ] }} /> )} } async function run() { await Navigation.present({ element: }) Script.exit() } run() ``` --- url: /doc_v2/zh/guide/View Modifiers/Symbol Style.md --- # 符号样式 这些修饰符用于配置 SF Symbols(系统符号图标)的显示样式和动画效果,常用于 `` 组件。 *** ### `symbolRenderingMode` 设置符号图像的 **渲染模式**。 #### 类型 ```ts symbolRenderingMode?: SymbolRenderingMode ``` #### 可选值(SymbolRenderingMode): - `"monochrome"`:单色模式,使用当前前景色绘制 - `"hierarchical"`:层次渲染,根据不同图层设置不透明度(适合语义着色) - `"multicolor"`:使用符号内置颜色 - `"palette"`:分层渲染,可自定义每一层的颜色样式(需搭配 `foregroundStyle`) #### 示例 ```tsx ``` *** ### `foregroundStyle` 设置符号或前景元素的颜色样式。 #### 类型 ```ts foregroundStyle?: | ShapeStyle | DynamicShapeStyle | { primary: ShapeStyle | DynamicShapeStyle secondary: ShapeStyle | DynamicShapeStyle tertiary?: ShapeStyle | DynamicShapeStyle } ``` #### 说明: - 在 `"monochrome"` 模式下使用单个颜色或渐变; - 在 `"palette"` 模式下使用 `{ primary, secondary, tertiary }` 对象指定多层样式; - `tertiary` 可选,仅在符号有三层图层时有效。 *** ### `symbolVariant` 为符号添加特定的 **视觉变体**。 #### 类型 ```ts symbolVariant?: SymbolVariants ``` #### 可选值(SymbolVariants): - `"none"`:无变体,原始符号样式 - `"fill"`:填充样式 - `"circle"`:包裹在圆形轮廓中 - `"square"`:包裹在方形轮廓中 - `"rectangle"`:包裹在矩形轮廓中 - `"slash"`:斜杠样式,表示禁止/关闭等状态 #### 示例 ```tsx ``` *** ### `symbolEffect` 为符号添加 **动画效果**,支持静态应用或绑定数值以触发动画。 #### 类型 ```ts symbolEffect?: SymbolEffect ``` #### 使用方式: ##### 1. 静态符号效果(SymbolEffect 简写字符串) ```tsx ``` ##### 2. 动态绑定符号效果(每次值变化时触发动画) ```tsx ``` 每次 `isLiked` 状态变化时,图标会执行 bounce 动画。 *** ### 可用 Symbol 动效分类(DiscreteSymbolEffect) | 类别 | 动效关键字 | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 弹跳 Bounce | `bounce`, `bounceByLayer`, `bounceDown`, `bounceUp`, `bounceWholeSymbol` | | 呼吸 Breathe | `breathe`, `breatheByLayer`, `breathePlain`, `breathePulse`, `breatheWholeSymbol` | | 脉冲 Pulse | `pulse`, `pulseByLayer`, `pulseWholeSymbol` | | 旋转 Rotate | `rotate`, `rotateByLayer`, `rotateClockwise`, `rotateCounterClockwise`, `rotateWholeSymbol` | | 颜色变化 VariableColor | `variableColor`, `variableColorIterative`, `variableColorDimInactiveLayers`, `variableColorHideInactiveLayers`, `variableColorCumulative` | | 摇晃 Wiggle | `wiggle`, `wiggleLeft`, `wiggleRight`, `wiggleUp`, `wiggleDown`, `wiggleForward`, `wiggleBackward`, `wiggleByLayer`, `wiggleWholeSymbol`, `wiggleClockwise`, `wiggleCounterClockwise` | *** ### 综合示例 ```tsx ``` 上述示例中: - 使用了分层渲染(hierarchical); - 添加了圆形变体(circle); - 设置了 `indigo` 颜色; - 每当 `isNotified` 变化时,符号执行 `breathePulse` 动画。 *** ## 修饰符汇总表 | 修饰符 | 说明 | | --------------------- | ------------------------- | | `symbolRenderingMode` | 设置符号图标的渲染模式(单色、多色、层次、调色板) | | `foregroundStyle` | 设置符号的颜色风格,可支持多图层配色 | | `symbolVariant` | 添加符号样式变体,如填充、圆形、斜杠等 | | `symbolEffect` | 添加符号动画,可静态或绑定值驱动 | --- url: /doc_v2/zh/guide/View Modifiers/Text Field.md --- # 文本输入框 这些修饰符可用于自定义 `TextField` 组件的行为和外观,包括键盘类型、自动更正、自动大写、提交操作等。 *** ## `onSubmit` 为文本字段添加提交时触发的操作。 ### 类型 ```ts onSubmit?: (() => void) | { triggers: SubmitTriggers action: () => void } ``` ### 行为说明 - 若直接提供函数形式: ```tsx console.log('提交了')} /> ``` 等价于: ```tsx console.log('提交了') }} /> ``` - 也可以明确指定触发提交操作的方式: ```tsx console.log('搜索提交') }} /> ``` ### `SubmitTriggers` 可选值: - `"text"`:由文本输入控件(如 `TextField`、`SecureField`)触发。 - `"search"`:由搜索输入框(使用 `searchable` 修饰符)触发。 *** ## `keyboardType` 设置聚焦输入时显示的键盘类型。 ### 类型 ```ts keyboardType?: KeyboardType ``` ### 可选值: - `'default'` - `'numberPad'` - `'phonePad'` - `'namePhonePad'` - `'URL'` - `'decimalPad'` - `'asciiCapable'` - `'asciiCapableNumberPad'` - `'emailAddress'` - `'numbersAndPunctuation'` - `'twitter'` - `'webSearch'` ### 示例 ```tsx ``` *** ## `autocorrectionDisabled` 控制是否启用系统的自动更正功能。 ### 类型 ```ts autocorrectionDisabled?: boolean ``` ### 默认值 - `true` — 默认禁用自动更正。 ### 示例 ```tsx ``` *** ## `textInputAutocapitalization` 设置文本输入时的自动大写行为。 ### 类型 ```ts textInputAutocapitalization?: TextInputAutocapitalization ``` ### 可选值 - `"never"` – 不自动大写。 - `"characters"` – 每个字母都大写。 - `"sentences"` – 每个句子的首字母大写。 - `"words"` – 每个单词的首字母大写。 ### 示例 ```tsx ``` *** ## `submitScope` 阻止当前视图触发的提交操作向上传递到父级视图的 `onSubmit` 处理器。 ### 类型 ```ts submitScope?: boolean ``` ### 默认值 - `false` — 默认允许事件向上传递。 ### 示例 ```tsx ``` 启用此项后,该字段的提交事件将不会触发父视图中的提交处理逻辑。 ## `submitLabel` 设置提交按钮的文本。 ### 类型 ```ts submitLabel?: "continue" | "return" | "send" | "go" | "search" | "join" | "done" | "next" | "route" ``` ### 示例 ```tsx ``` --- url: /doc_v2/zh/guide/View Modifiers/Text View Modifiers.md --- # 文本修饰符 以下属性可用于为基于文本的视图(如 `Text` 或 `Label`)设置样式和格式,其功能与 SwiftUI 的内建修饰符类似。通过自定义这些属性,您可以控制文本的字体、字重、设计、间距及其他排版特性。 ## 概览 这些属性通常作为属性传递给与文本相关的组件,如 `Text` 或 `Label`。例如,您可以设置字体大小、启用加粗格式,或添加自定义颜色的下划线——无需手动调用多个修饰符。 ```tsx Stylish Text Here ``` 在上面的示例中,文本使用了自定义字体、半粗体、斜体风格、红色下划线,限制为两行,并居中对齐。 *** ## 字体配置 ### `font` 定义文本的字体和大小。 - **数字**:提供一个数字(例如 `14`)时,将应用该大小的系统字体。 - **预设字体名称**(`Font` 类型):使用内建的文本样式之一(如 `"largeTitle"`、`"title"`、`"headline"`、`"subheadline"`、`"body"`、`"callout"`、`"footnote"`、`"caption"`)。系统会根据样式决定大小和字重。 - **包含名称和大小的对象**:指定 `name` 和 `size` 来应用自定义字体。 ```tsx 系统字体,大小为 20 系统标题字体 自定义字体 ``` *** ### `fontWeight` 设置字体的粗细程度。可选值包括从 `"ultraLight"` 到 `"black"`。 ```tsx 加粗文本 ``` *** ### `fontWidth` 指定字体的宽度变体(如果可用)。可选值有 `"compressed"`、`"condensed"`、`"expanded"` 和 `"standard"`,也可以使用数字(如果支持)。 ```tsx 压缩宽度字体 ``` *** ### `fontDesign` 修改字体设计风格。可选值包括 `"default"`、`"monospaced"`、`"rounded"`、`"serif"`。 ```tsx 圆角字体设计 ``` *** ## 文本格式 ### `minScaleFactor` 一个介于 0 到 1 之间的数字,表示当文本超出空间限制时最多可以缩小到原始大小的多少。例如,`0.5` 表示文本可以缩小到 50%。 ```tsx 当文本超出时会稍微缩小。 ``` *** ### `bold` 如果为 `true`,应用加粗字体。 ```tsx 这是加粗文本 ``` *** ### `baselineOffset` 调整文本相对于基线的垂直位置。正值向上移动,负值向下移动。 ```tsx 文本向上偏移 ``` *** ### `kerning` 控制字符间距。正值增加间距,负值减小间距。 ```tsx 字符间距增加 ``` *** ### `italic` 如果为 `true`,应用斜体样式。 ```tsx 斜体文本 ``` *** ### `monospaced` 强制所有子文本使用等宽字体(如果可用)。 ```tsx 等宽字体文本 ``` *** ### `monospacedDigit` 使用固定宽度数字,而其他字符保持原样。适用于表格或计时器中的数字对齐。 ```tsx 数字等宽对齐 1234 ``` *** ## 文本装饰 ### `strikethrough` 应用删除线(贯穿文本)。可以提供颜色,或一个包含样式和颜色的对象。 - **仅颜色**:`strikethrough="red"` - **对象**:`strikethrough={{ pattern: 'dash', color: 'blue' }}` ```tsx 灰色删除线文本 红色点状删除线 ``` *** ### `underline` 以下划线方式装饰文本,使用方式与 `strikethrough` 类似。 - **仅颜色**:`underline="blue"` - **对象**:`underline={{ pattern: 'dashDot', color: 'green' }}` ```tsx 蓝色下划线文本 粉色点状下划线 ``` *** ## 行数、行间距与布局控制 ### `lineLimit` 指定文本最多显示的行数。可以: - 提供一个数字来设置最大行数; - 或提供一个对象 `{ min?: number; max: number; reservesSpace?: boolean }`,来指定最小和最大行数,并选择是否预留最大行数空间以避免布局跳动。 ```tsx 如果超出一行将被截断。 可显示 2 到 4 行文本,并始终预留 4 行空间,避免布局变化。 ``` *** ### `lineSpacing` 设置行间距,单位为像素。 ```tsx 设置行间距为 5 像素 ``` *** ### `multilineTextAlignment` 设置多行文本的对齐方式:`"leading"`(左对齐)、`"center"`(居中)或 `"trailing"`(右对齐)。 ```tsx 多行文本居中显示。 ``` *** ### `truncationMode` 指定文本太长时的截断方式。 #### 类型 ```ts type TruncationMode = "head" | "middle" | "tail" ``` #### 描述 定义截断的位置: - `"head"`:截断行首,保留末尾。 - `"middle"`:截断中间,保留首尾。 - `"tail"`:截断尾部,保留开头。 ```tsx 这是一段可能会被截断的很长文本。 ``` *** ### `allowsTightening?: boolean` 是否允许系统在必要时压缩字符间距以适应一行内显示。 #### 类型 `boolean` #### 默认值 `false` #### 描述 设置为 `true` 时,系统可以压缩字距以避免截断,并改善在受限空间下的布局适应性。 ```tsx 在需要时压缩的文本 ``` *** ## 总结 通过组合这些属性,您可以完全掌控文本视图的排版,而无需多个包装组件或修饰符。无论您需要加粗、斜体、带自定义字符间距和下划线的标题,还是仅限两行显示的正文文本,这些选项都能满足广泛的文本样式需求。 --- url: /doc_v2/zh/guide/View Modifiers/Toast.md --- # Toast提示 `toast` 修饰器用于在视图上显示一个临时提示框(Toast)。 它通常用于短暂地展示消息或反馈信息,例如“保存成功”、“操作完成”、“网络错误”等。 Toast 可以包含简单的文本消息,也可以自定义内容视图。 你可以控制其显示位置、持续时间、背景颜色、圆角、阴影等外观属性。 *** ## 类型定义 ```ts toast?: { duration?: number | null position?: "top" | "bottom" | "center" backgroundColor?: Color | null textColor?: Color | null cornerRadius?: number | null shadowRadius?: number | null } & ( | { message: string; content?: never } | { message?: never; content: VirtualNode } ) & ({ isPresented: boolean onChanged: (isPresented: boolean) => void } | { isPresented: Observable }) ``` *** ## 属性说明 ### `isPresented: boolean` 和 `onChanged(isPresented: boolean): void` **说明**: 使用`isPresented`和`onChanged`来控制Toast的显示和隐藏。 **示例**: ```tsx const [showToast, setShowToast] = useState(false) toast={{ isPresented: showToast, onChanged: setShowToast, message: "Saved successfully" }} ``` *** ### `isPresented: Observable` **说明**:使用 `isPresented` 作为 `Observable` 来控制 Toast 的显示和隐藏。 **示例**: ````tsx const showToast = useObservable(false) toast={{ isPresented: showToast, message: "Saved successfully" }} --- ### `duration?: number | null` **说明**: Toast 显示的持续时间(单位:秒)。 默认值为 `2` 秒。 **示例**: ```tsx toast={{ isPresented: showToast, onChanged: setShowToast, duration: 3, message: "Action completed" }} ```` *** ### `position?: "top" | "bottom" | "center"` **说明**: 控制 Toast 在屏幕上的显示位置。 可选值: - `"top"`:顶部显示 - `"bottom"`:底部显示(默认) - `"center"`:居中显示 **示例**: ```tsx toast={{ isPresented: showToast, onChanged: setShowToast, position: "top", message: "New message received" }} ``` *** ### `backgroundColor?: Color | null` **说明**: 设置 Toast 的背景颜色。可以使用任意支持的 `Color` 类型。 **示例**: ```tsx toast={{ isPresented: showToast, onChanged: setShowToast, backgroundColor: "blue", message: "Upload successful" }} ``` *** ### `textColor?: Color | null` **说明**: 设置 Toast 文本的颜色。 **示例**: ```tsx toast={{ isPresented: showToast, onChanged: setShowToast, textColor: "white", message: "Download failed" }} ``` *** ### `cornerRadius?: number | null` **说明**: 设置 Toast 的圆角大小。 默认值为 `16`。 **示例**: ```tsx toast={{ isPresented: showToast, onChanged: setShowToast, cornerRadius: 8, message: "Item added" }} ``` *** ### `shadowRadius?: number | null` **说明**: 设置阴影的模糊半径。 默认值为 `4`。 **示例**: ```tsx toast={{ isPresented: showToast, onChanged: setShowToast, shadowRadius: 6, message: "Success" }} ``` *** ## 显示文本消息 **示例**: ```tsx function View() { const [showToast, setShowToast] = useState(false) return ( ``` ### 执行 AppIntent 的按钮 ```tsx