# @bacons/apple-targets 中文文档

规范地址：https://npmcn.com/package/%40bacons/apple-targets/

版本：5\.0\.0
npm 元数据更新时间：2026-07-17T19:36:08.255Z
中文译文更新时间：2026-11-07T14:38:46.946Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40bacons/apple-targets
原文对照：https://npmcn.com/package/%40bacons/apple-targets/?lang=original
项目主页：https://github.com/evanbacon/expo-apple-targets
源码仓库：https://github.com/evanbacon/expo-apple-targets

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：550 kB，45 个文件
- 模块格式：CommonJS
- TypeScript 类型：包内自带
- 运行依赖：5 个
- peer 依赖：expo \>=52
- 首次发布：2023-12-04T05:31:36.364Z
- 最新版本发布：2026-07-17T19:36:08.092Z
- 近 30 天下载量：3224156（2026-10-08 至 2026-11-06，npm 官方下载统计）
- 已发布版本数：50（近 12 个月 11 个）

## 中文简介

以 Expo 预建创建生成苹果目标

## 安装

```sh
npm install @bacons/apple-targets
```

也可以使用：`pnpm add @bacons/apple-targets`、`yarn add @bacons/apple-targets`、`bun add @bacons/apple-targets`

## README

# 苹果目标

一个 Expo 配置插件, 生成本地的 Apple 目标, 如 Widget 或 App Clips, 并将其链接到外部 `/ios` 目录。 您可以打开 Xcode 并开发虚拟内的目标 `expo:targets` 文件夹和更改将被保存在 `ios` 目录。 这种模式使得建筑不属于“土著反应”范围的东西能够同时获得所有的好处 [连续的土著一代](https://docs.expo.dev/workflow/continuous-native-generation/)。 。 。 .

![targets](https://github.com/user-attachments/assets/aedaafa0-1ef0-403c-a797-9f4c82cdb9f1)

## 如何使用

> 这个插件至少需要CocoaPods 1.16.2 (ruby 3.2.0),Xcode 16 (macOS 15 Sequoia),和 Expo SDK + 53.

1.  运行 `npx create-target` 以生成苹果目标.
2.  选择要生成的目标,我建议从 `widget` (例如:. `npx create-target widget`) (中文(简体) ). 这将生成根中所需的部件文件 `/targets` 目录, 安装 `@bacons/apple-targets`,并在您的工程中添加 Expo 配置插件.
3.  如果你已经知道你的苹果团队身份, 那就把它放在下面 `ios.appleTeamId` 属性 `app.json`。 您可以在签名和功能标签下的 Xcode 中找到它, 如有必要, 稍后设置它 .
4.  运行 `npx expo prebuild -p ios --clean` 生成 Xcode 项目.
5.  您现在可以在 Xcode 中打开您的工程(`xed ios`并开发其中的部件 `expo:targets/<target>` 文件夹.
6.  准备建造时,在Xcode中选择目标并建造.

### 如何运作

词根 `/targets` 文件夹是魔法,每个子目录应该有一个 `expo-target.config.js` 定义目标设置的文件。 当你跑的时候 `npx expo prebuild --clean`,插件将生成 Xcode 项目,并将目标文件与项目链接. 插件也会生成一个 `Info.plist` 如果一个不存在的话.

配置插件将链接子文件夹(如:. `targets/widget`)到Xcode,里面的所有文件都将是目标的一部分. 这意味着你可以开发目标 在内部 `expo:targets` 文件夹和更改将被保存在生成的外 `ios` 目录.

词根 `Info.plist` 每个目标目录中的文件没有管理,可以自由修改.

顶层的任何文件 `target/{name}/assets` 目录将作为目标的资源链接。 加入这一规则是为了支持Safari扩展.

### 应享权利

如果 `expo-target.config` 文件定义 `entitlements: {}` 对象, a `generated.entitlements` 文件写入预建输出于 `ios/.targets/<productName>/generated.entitlements` (不输入目标源文件夹),和 `CODE_SIGN_ENTITLEMENTS` 它指着它。 这个 `.targets` 目录使得其中的文件明显是衍生出来的,不应由手来编辑. 源目标目录没有被触及,因此可以保持git中衍生出文物的清洁. 更新 `expo-target.config` 要更改应享待遇——从不直接编辑生成的文件,它会被覆盖在每一个文件上 `npx expo prebuild`。要代替手工管理应享权利文件,请删除 `entitlements` 对象从 `expo-target.config` 添加一个 `*.entitlements` 转到目标源文件夹.

如果 `entitlements` 对象在配置中没有定义,您可以手动添加单个顶级 `*.entitlements` 文件到目标源目录——重运行 `npx expo prebuild` 将该文件链接到目标。 只有一级 `*.entitlements` 每个目标都支持文件。 如果两者都由配置驱动 `entitlements` 块和一个手写 `*.entitlements` 文件已存在,配置胜出后手写文件被忽略(你会看到日志行并可以删除它).

有些目标有特殊的权利行为:

-   缩写( A)`clip`) 自动设置所需的 `com.apple.developer.parent-application-identifiers` 改为 `$(AppIdentifierPrefix)${config.ios.bundleIdentifier}`
-   能够使用 App Groups 的目标将自动映射 `ios.entitlements['com.apple.security.application-groups']` 从 `app.json` 如果它的定义。 可以通过指定一个 `entitlements['com.apple.security.application-groups']` 数组 `expo-target.config.js` 文档.

### 发展

外出的任何变化 `expo:targets` xcode 中的目录将被下个覆盖 `npx expo prebuild --clean`。检查是否在 Info.plist 或 `expo-target.config.js` 文档. 如果你修改 `expo-target.config.js` 或你的根头 `app.json`您需要重新运行 `npx expo prebuild --clean` 以同步更改.

您可以使用自定义的预建模板 `--template ./node_modules/@bacons/apple-targets/prebuild-blank.tgz` 创建一个没有React Industrial的建筑,这可以使开发速度快一些,因为编译时间较少. 这是先进的发展技术 **不生产** 并不打算用于第三方配置插件.

## AI 代理技能

这回转船是 [代理技能](https://github.com/vercel-labs/skills) 用于教学 AI 编码代理( Claude Code, Cursor, Codex等) 如何用此插件构建、配置和调试每个支持的 Apple 扩展名和目标类型 。 它包括一个每个目标参考文件与苹果docs,运行时间行为,生产准备的斯威夫特,平台支持,以及Gotchas.

安装到您的代理中 [`npx skills`](https://github.com/vercel-labs/skills)数字 :

```sh
npx skills add EvanBacon/expo-apple-targets
```

CLI自动检测您安装的代理,并将技能复制到他们的技能目录(例如. `.claude/skills/`),当您要求代理添加 Apple 扩展时,它会自动加载。 见 [医生/技能](https://npmcn.com/package/docs/skills.md) 细节、更新和删除.

## 目标配置

目标配置可以是 `expo-target.config.js`,或 `expo-target.config.json` 文档.

此文件可以有以下属性 :

```js
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
  type: "widget",

  // Name of the target/product. Defaults to the directory name.
  name: "my_widget",

  // Optional override for CFBundleDisplayName. Defaults to `name` if not provided.
  displayName: "My Widget",

  // Generates colorset files for the target.
  colors: {
    // or "$accent": "red",
    $accent: { color: "red", darkColor: "blue" },
  },
  icon: "../assets/icon.png",
  // Can also be a URL
  frameworks: [
    // Frameworks with or without the `.frameworks` extension, these will be added to the target.
    "SwiftUI",
  ],
  entitlements: {
    // Serialized entitlements. Useful for configuring with environment variables.
  },
  // Generates xcassets for the target. Supports images (imageset) and SF Symbol template SVGs (symbolset).
  images: {
    thing: "../assets/thing.png",
    // SF Symbol SVGs are automatically detected and generated as symbolsets.
    mySymbol: "./my-symbol.svg",
  },

  // The iOS version fot the target. Defaults to 18.0
  deploymentTarget: "15.1",

  // Optional bundle identifier for the target. Will default to a sanitized version of the root project bundle id + target name.
  // If the specified bundle identifier is prefixed with a dot (.), the bundle identifier will be appended to the main app's bundle identifier.
  bundleIdentifier: ".mywidget",

  // Should the release build export the JS bundle and embed. Intended for App Clips and Share Extensions where you may want to use React Native.
  exportJs: false,
};
```

您也可以返回一个接受 ExpoConfig 的函数,并返回一个同步权限和其他值的目标函数:

```js
/** @type {import('@bacons/apple-targets/app.plugin').ConfigFunction} */
module.exports = (config) => ({
  type: "widget",
  colors: {
    $accent: "steelblue",
  },
  entitlements: {
    // Use the same app groups:
    "com.apple.security.application-groups":
      config.ios.entitlements["com.apple.security.application-groups"],
    // Or generate an app group:
    "com.apple.security.application-groups": [
      `group.${config.ios.bundleIdentifier}.widget`,
    ],
  },
});
```

> 在目标配置中不支持无害环境管理和类型脚本。 坚持 `require` 用于在目标之间分享变量.

## 颜色

目标之间有某些共同的价值观。 我们使用预先界定的公约来描绘这些目标之间的这些价值.

| 名称 | 构建设置 | 目的 |
| --- | --- | --- |
| `$accent` | `ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME` | 设置全局口音颜色, 在元件中, 用于编辑元件时按钮的颜色 . |
| `$widgetBackground` | `ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME` | 设置部件的背景颜色. |

## SF 符号

SVG 文件传递给 `images` 自动检查以确定是否为 [SF 符号模板](https://developer.apple.com/sf-symbols/)。当检测到时, SVG 将生成为 `.symbolset` 内 `Assets.xcassets` 而不是常规 `.imageset`。这对本地文件路径和URL都有效.

```js
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
  type: "widget",
  images: {
    // SF Symbol template SVGs are auto-detected:
    expo: "./expo.sfsymbol.svg",
    // URLs work too:
    remote: "https://example.com/my-symbol.svg",
    // Regular images still work as expected:
    photo: "./photo.png",
  },
};
```

在斯威夫特语中,使用符号名作为自定义符号图像:

```swift
Image("expo")
```

您可以轻松使用 :

```sh
bun create symbol /path/to/icon.svg
```

## 可可罂粟

正在添加文件 `pods.rb` 在 repo 根中,您可以修改项目的目标设置.

红宝石模块对地产进行全球访问评估 `podfile_properties`。 。 。 .

例如,以下内容对于在 App Clip 目标中实现 React Native 有用 :

```rb
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")

exclude = []
use_expo_modules!(exclude: exclude)

if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
  config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
else
  config_command = [
    'node',
    '--no-warnings',
    '--eval',
    'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
    'react-native-config',
    '--json',
    '--platform',
    'ios'
  ]
end

config = use_native_modules!(config_command)

use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']

use_react_native!(
  :path => config[:reactNativePath],
  :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
  # An absolute path to your application root.
  :app_path => "#{Pod::Config.instance.installation_root}/..",
  :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
)
```

此块在 Podfile 结尾处执行 :

```rb
target "target_dir_name" do
   target_file
end
```

目标名称必须与目标目录名称相匹配.

## `_shared`

一些文件需要与您的目标和主要目标同时链接. 要支持, 您可以添加顶级 `_shared` 目录。 此目录中的任何文件都将同时链接到主要目标和子目标. 每次添加,重命名,或删除此目录中的文件,都需要重新运行预建.

还可以添加一个 `_shared` 根内部的目录 `targets/_shared` 目录。 这将将文件链接到您工程中的所有目标 .

## `exportJs`

这个 `exportJs` 当目标使用 React Native(App Clip,Share扩展名)时,应当使用选项。 它通过连接 主要目标的 `Bundle React Native code and images` 建相到目标。 这将确保生产得以建立(`Release`)将JS主条目文件与Metro捆绑,并嵌入捆绑/资产供下线使用.

要检测正在构建的目标, 您可以使用 `expo-application`。 。 。 .

## 实例

### `widget`

> 我写了一篇关于这个的博客, 学习更多: [Expo x 苹果部件](https://evanbacon.dev/blog/apple-home-screen-widgets)。 。 。 .

```js
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
  type: "widget",
  icon: "../../icons/widget.png",
  colors: {
    // This color is referenced in the Info.plist
    $widgetBackground: "#DB739C",

    $accent: "#F09458",

    // Optional: Add colors that can be used in SwiftUI.
    gradient1: {
      light: "#E4975D",
      dark: "#3E72A0",
    },
  },
  // Optional: Add images that can be used in SwiftUI.
  // SF Symbol SVGs are automatically detected and generated as symbolsets.
  images: {
    valleys: "../../valleys.png",
    myIcon: "./my-icon.sfsymbol.svg",
  },
  // Optional: Add entitlements to the target, this one can be used to share data between the widget and the app.
  entitlements: {
    "com.apple.security.application-groups": ["group.bacon.data"],
  },
};
```

### `action`

![IMG\_C2C825ACC8C7-1](https://github.com/user-attachments/assets/8378e022-2061-4da8-9c46-efe3064dd40c)

这些出现在股票单上。 图标应当透明,因为系统将遮盖图标.

```js
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
  type: "action",
  displayName: "Inspect Element",
  icon: "./assets/icon.png",
  colors: {
    TouchBarBezel: "#DB739C",
  },
};
```

将 JavaScript 文件添加到 `assets/index.js`数字 :

```js
class Action {
  /**
   * `extensionName: "com.bacon.2095.axun"`
   * @param {*} arguments: {completionFunction: () => unknown; extensionName: string; }
   */
  run({ extensionName, completionFunction }) {
    // Here, you can run code that modifies the document and/or prepares
    // things to pass to your action's native code.

    // We will not modify anything, but will pass the body's background
    // style to the native code.
    completionFunction({
      /* */
    });
  }

  finalize() {
    // Runs after the native action code has completed.
  }
}

window.ExtensionPreprocessingJS = new Action();
```

保证 `NSExtensionJavaScriptPreprocessingFile: "index"` 在Info.plist中.

### `spotlight`

用您的应用内容来传播 Spotlight 搜索结果 .

```js
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
  type: "spotlight",
};
```

### 支持类型

理想的情况是,这将会在一个完全合格的Xcode项目上自动生成,但目前这是一个人工过程. 目前所支持的类型是基于对iOS App Store中最常用目标的静态分析. 我还没有测试过所有这些 而且它们可能不起作用.

| 类型 | 说明 |
| --- | --- |
| 动作 | 共享动作 |
| 应用程序 | 应用程序意向扩展 |
| 部件 | 部件/ 活活动 |
| 监视 | 监视 App( 附带 iOS App) |
| 监视器 | 观察相貌复杂 |
| 剪贴板 | 缩写 |
| 萨法里 | Safari 扩展 |
| 份额 | 共享扩展 |
| 通知内容 | 通知内容扩展 |
| 通知服务 | 通知服务扩展 |
| 意图 | Siri 意向扩展 |
| 意向 - ui | Siri 意向 UI 扩展 |
| 聚光灯 | 焦点索引扩展 |
| bg- 下载 | 背景下载扩展 |
| 快看通肠 | 快速外观缩略图扩展 |
| 位置推动 | 地点 : |
| 全权证书提供者 | 证书提供者扩展 |
| 账户-自动 | 账户认证扩展 |
| 设备活动监测器 | 设备活动监视器扩展 |
| 键盘 | 自定义键盘扩展 |
| 事项 | 物质设备设置扩展 |
| 网络包通 | 包式隧道网络扩展 |
| 网络应用代理 | App代理网络扩展 |
| 网络代理 | DNS 代理网络扩展 |
| 网络过滤数据 | 过滤数据网络扩展 |
| 内容屏蔽器 | Safari 内容屏蔽扩展 |
| 文件提供者 | 文件提供者扩展名 |
| 广播加载量 | 广播上传扩展 |
| 呼叫目录 | 调用目录扩展 |
| 信件过滤器 | 信件过滤扩展 |
| 文件提供者- ui | 文件提供者 UI 扩展名 |
| 广播设置 | 广播设置 UI 扩展 |
| classkit 语句 | ClassKit 上下文提供者扩展 |
| 意外通讯 | 不需要的通讯报告 |
| 相片编辑 | 相片编辑扩展 |
| 快速视图审查 | 快速浏览预览扩展 |
| 聚光灯 | CoreSpotlight 代表扩展 |
| 虚拟会议 | 虚拟会议提供者 |
| 屏蔽动作 | 盾牌行动扩展 |
| 图标配置 | 盾牌配置扩展 |
| 打印服务 | 打印服务扩展 |
| 智能卡 | 智能卡/ 持久托肯 |
| 认证服务 | 认证服务扩展 |
| 钱包 | 钱包供应(非UI) |
| 钱包 - ui | 钱包提供 UI |

### 尚未支持

以下扩展类型在Xcode中存在,但尚未被支持. 欢迎捐款! 见 `docs/xcode-target-discovery.md` 关于如何发现这些细节.

| 扩展点标识符 | Xcode 模板名称 |
| --- | --- |
| `com.apple.tv-top-shelf` | TV 高架扩展(tvOS) |
| `com.apple.FinderSync` | 查找器同步扩展( macOS) |
| `com.apple.email.extension` | 邮件扩展( MacOS) |

> 运行 `bun scripts/scan-xcode-targets.ts --diff` 从本地 Xcode 安装中重生成此列表.

## 代码签名

密码是完全由 [EAS 构建](https://docs.expo.dev/build/introduction/)。此插件将添加目标签名工作所需的权利。 我只用我的柱子谷部件测试过这个端到端.

如果你愿意,你也可以手动签下所有子目标,我会为你点起蜡烛.

> 我还没有得到 App Clip 代号的完全自动化。 公关欢迎.

## 建筑部件

我写了一篇博客文章 [Expo x 苹果部件](https://evanbacon.dev/blog/apple-home-screen-widgets)。 。 。 .

如果你遇到建造部件的问题, 这可能是因为 React Industrial 被运出时没有压缩, 这使得建筑的复杂性要高得多。 这往往导致与斯威夫特编译器和斯威夫特UI预览出现问题.

一些工作轮廓:

-   清除 SwiftUI 预览缓存 : `xcrun simctl --set previews delete all`
-   不重复原生的预建 : `npx expo prebuild --template ./node_modules/@bacons/apple-targets/prebuild-blank.tgz --clean`
-   如果部件在构建应用程序时没有在主屏幕上显示,则使用iOS 18. 您可以按下应用程序图标并选择部件显示选项, 将应用程序图标转换为部件 .

## 目标之间的数据共享

要在应用程序与目标之间共享值,您必须使用 App Groups 和 NSUserDefaults 。 我增加了一个本地模块 来让反应原生API变得比较容易.

### 配置应用组

从定义 App 组开始, 一个好的默认值是 `group.<bundle identifier>`。 App Groups 可以跨应用程序使用,所以如果计划多个扩展,您可能想要更通用或更通用的东西.

首先,定义您在您的 App Group 中的主要权益 `app.json`数字 :

```json
{
  "expo": {
    "ios": {
      "entitlements": {
        "com.apple.security.application-groups": ["group.bacon.data"]
      }
    },
    "plugins": ["@bacons/apple-targets"]
  }
}
```

第二,定义目标中相同的App组 `expo-target.config.js`数字 :

```js
/** @type {import('@bacons/apple-targets/app.plugin').ConfigFunction} */
module.exports = (config) => ({
  type: "widget",
  entitlements: {
    // Use the same app groups:
    "com.apple.security.application-groups":
      config.ios.entitlements["com.apple.security.application-groups"],
  },
});
```

现在你可以预建 生成权利。 您可能需要创建 EAS 构建或打开 Xcode 来同步应享待遇 .

### 设置共享数据

为了定义共享数据,我们将使用本地模块(`ExtensionStorage`与 `NSUserDefaults`。 。 。 .

在你的博览会应用软件中,你可以设定一个值:

```js
import { ExtensionStorage } from "@bacons/apple-targets";

// Create a storage object with the App Group.
const storage = new ExtensionStorage(
  // Your app group identifier. Should match the values in the app.json and expo-target.config.json.
  "group.bacon.data"
);

// Then you can set data:
storage.set("myKey", "myValue");

// Finally, you can reload the widget:
ExtensionStorage.reloadWidget();
```

`ExtensionStorage` 拥有下列API:

-   `set(key: string, value: string | number | Record<string, string | number> | Array<Record<string, string | number>> | undefined): void` - 在给定密钥的共享存储中设置一个值。 设置 `undefined` 将删除密钥.
-   `ExtensionStorage.reloadWidget(name?: string): void` - 重新装入部件的静态方法。 在幕后,这个电话 `WidgetCenter.shared.reloadAllTimelines()`。如果给定名称,它将使用 `WidgetCenter.shared.reloadTimelines(ofKind: timeline)`。 。 。 .
-   `ExtensionStorage.reloadControls(name?: string): void` - 重新装入控制器的静态方法。 在幕后,这个电话 `ControlCenter.shared.reloadAllControls(): void`。如果给定名称,它将使用 `ControlCenter.shared.reloadControls(ofKind?: string): void`。 。 。 .
-   `remove(key: string): void` - 从共享存储中移除密钥的方法.
-   `get(key: string): string | null` - 从共享存储中获取值的静态方法.

### 获取共享数据

假设使用 Swift 代码完成, 您可以使用 `NSUserDefaults` 直接说 以下是您如何在部件中访问数据的例子 :

```swift
let defaults = UserDefaults(suiteName:
  // Use the App Group from earlier.
  "group.bacon.data"
)
// Access the value you set:
let index = defaults?.string(forKey: "myKey")
```

### 更多数据更新

对于更先进的用途,我建议使用下列资源:

-   当应用程序处于背景时更新部件 : [将部件保存到日期](https://developer.apple.com/documentation/widgetkit/keeping-a-widget-up-to-date)。 。 。 .

## Xcode 解析

此插件使用我的专有 Xcode 解析库, [`@bacons/xcode`](https://github.com/evanbacon/xcode)大多是打入的,非常未经测试, 并且可能充满了bugs-尽管如此,它仍然是10x更好的选择.

## 控制部件

![Simulator Screenshot - iPhone 16 - 2025-01-26 at 15 57 44](https://github.com/user-attachments/assets/c989a3bb-112d-4026-a718-49de4cdb2f3e)

[控制部件](https://developer.apple.com/documentation/swiftui/controlwidget) 是显示在控制中心、Siri建议、锁屏和快捷键上的部件类型.

一般情况下,您会想要将控制部件添加到一个 `widget` 目标,但是它们可以 添加到任何目标.

你可以加入多种意图,它们应该出现在 `[target]/_shared/*.swift` 文件夹,这样可以将它们添加到主要目标以及部件目标中,这就要求它们正确工作.

以下是为我的应用程序发射通用链接的控制部件的例子.

```swift
// targets/widget/_shared/intents.swift

import AppIntents
import SwiftUI
import WidgetKit

// TODO: These must be added to the WidgetBundle manually. They need to be linked outside of the _shared folder.
// @main
// struct exportWidgets: WidgetBundle {
//     var body: some Widget {
//         widgetControl0()
//         widgetControl1()
//     }
// }

@available(iOS 18.0, *)
struct widgetControl0: ControlWidget {
    // Unique ID for the control.
    static let kind: String = "com.bacon.clipdemo.0"
    var body: some ControlWidgetConfiguration {
      StaticControlConfiguration(kind: Self.kind) {
        ControlWidgetButton(action: OpenAppIntent0()) {
          // You can also use a custom image but it must be an SF Symbol.
          Label("App Settings", systemImage: "star")
        }
      }
      // This is the configuration for the widget.
      .displayName("Launch Settings")
      .description("A control that launches the app settings.")
    }
}

// This must be in both targets when `openAppWhenRun = true`. We can do that by adding it to the _shared folder.
// https://developer.apple.com/forums/thread/763851
@available(iOS 18.0, *)
struct OpenAppIntent0: ControlConfigurationIntent {
    static let title: LocalizedStringResource = "Launch Settings"
    static let description = IntentDescription(stringLiteral: "A control that launches the app settings.")
    static let isDiscoverable = true
    static let openAppWhenRun: Bool = true

    @MainActor
    func perform() async throws -> some IntentResult & OpensIntent {
        // Here's the URL we want to launch. It can be any URL but it should be a universal link for your app.
        return .result(opensIntent: OpenURLIntent(URL(string: "https://pillarvalley.expo.app/settings")!))
    }
}
```

你应该把意图 复制到主 `WidgetBundle` 结出.

**从您的 App 重新装入控件**

您的应用程序状态的变化可能影响控制显示 。 您可以使用

```js
ExtensionStorage.reloadControls();
```

自定义图像可以使用,但必须是SF符号. 你可以用 `bun create symbol /path/to/icon.svg` 或 [SF 符号应用程序](https://developer.apple.com/sf-symbols/)。将 SVG 添加到目标 `images` 配置后将自动检测和生成 `.symbolset` (见 [SF 符号](https://npmcn.com/package/%40bacons/apple-targets/#readme-sf-symbols)) (中文(简体) ).

您可以用控制部件做很多事情,比如启动自定义的UI而不是打开应用程序. 此插件应允许大多数这些东西工作.

## 缩写

![IMG\_6BC9D9534F1D-1](https://github.com/user-attachments/assets/f9847f6f-4f0a-44f9-932c-3f8e9703c133)

App Clips 利用了 Expo Router 的真正力量, 使您能够链接一个网站和本地应用程序, 以便立即在 iOS 上打开本地应用程序 。 不过他们很难工作.

这是我经验楼的一些笔记 [https://pillorvalley.expo.app 页面存档备份,存于互联网档案馆](https://pillarvalley.expo.app/) (打开iOS测试).

先构建应用,再构建网站. 如果情况不对,您随时可以更新网站。 其中包括AASA和元数据.

你也许需要 [此 RN 补丁](https://github.com/facebook/react-native/pull/47000) 否则在试飞时会坠毁 或者,你可以在 App Store Connect 中加入 App Clip 体验,并按预期发射.

在运行预建后,打开Xcode中的项目并导航到每个目标的签名分页,这将确保第一个版本的代码签名绝对正确. 我们需要调整EAS Build,以确保它也能做到这一点.

确保您的 App Clip 不存在 `expo-updates` 已安装, 否则它将无法在 AppDelegate 中安装一些关于 React 缺失的加密错误 .

确保所有建筑编号都一样 `CURRENT_PROJECT_VERSION` 财务报告和审定财务报表 `CFBundleVersion` (Info.plist) 否则应用程序将无法构建.

确保您添加 `public/.well-known/apple-app-site-association` 文件到您的网站并部署到网络上 (`eas deploy --prod`) (中文(简体) ). 这里 [实例](https://github.com/EvanBacon/pillar-valley/blob/d5ab82ae04f519310acf4b31aad8d9e22eb3747d/public/.well-known/apple-app-site-association#L27-L29)。 。 。 .

值会是 `<Apple Team ID>.<App Clip Bundle ID>`数字 :

```
{
  "appclips": {
    "apps": ["QQ57RJ5UTD.com.evanbacon.pillarvalley.clip"]
  }
}
```

将网站 URL 添加到您的 App Clip 权限( 不是主权限) 。 举个例子 `https://pillarvalley.expo.app`数字 :

```xml
<key>com.apple.developer.associated-domains</key>
<array>
  <string>appclips:pillarvalley.expo.app</string>
</array>
```

如果没有做到这一点,那么您的 App Clip 将只能从默认 App Store URL 启动 : `https://appclip.apple.com/id?p=com.evanbacon.pillarvalley.clip` (其中您的 App Clip 捆绑ID 将成为 URL 中的 ID).

您应该用一个 [`app/+native-intent.ts`](https://docs.expo.dev/router/advanced/native-intent/) 文件 :

```ts
export function redirectSystemPath({ path }: { path: string }): string {
  try {
    // Handle App Clip default page redirection.
    // If path matches https://appclip.apple.com/id?p=com.evanbacon.pillarvalley.clip (with any query parameters), then redirect to `/` path.
    const url = new URL(path);
    if (url.hostname === "appclip.apple.com") {
      // Redirect to the root path and make the original URL available as a query parameter (optional).
      return "/?ref=" + encodeURIComponent(path);
    }
    return path;
  } catch {
    return path;
  }
}
```

你应该用 `expo-linking` 获取与 App Clip 相关的 URL , 因为 上游 React 本地链接 有一些处理 App Clip 的问题 .

当您发布 App Clip 时, 二进制需要约 5 分钟才能在 App Store 出现(在它获得批准后), 但 App Clip 需要 25 分钟才能在您的网站上出现 .

您还需要在您的网站上添加一些元标记 。 这些需要快速运行 所以我建议把它们放进你的 `app/+html.tsx` 文件 :

```js
<meta
  name="apple-itunes-app"
  content={
    "app-id=1336398804, app-clip-bundle-id=com.evanbacon.pillarvalley.clip, app-clip-display=card"
  }
/>
```

您还应添加 `og:image` 使用属性 `expo-router/head`。 。 。 . [学多点](https://developer.apple.com/documentation/appclip/supporting-invocations-from-your-website-and-the-messages-app)。这似乎是一条通往 png 图像的绝对路径,即 `1200×630` b. 涉及各层面的[以此为基础](https://developer.apple.com/library/archive/technotes/tn2444/_index.html)) (中文(简体) ).

```js
<Head>
  {/* Required for app clips: */}
  {/* https://developer.apple.com/documentation/appclip/supporting-invocations-from-your-website-and-the-messages-app */}
  <meta property="og:image" content="https://pillarvalley.expo.app/og.png" />
</Head>
```

你也需要一个 `1800x1200` 用于App Store Connect图像预览的图像,因此在大约同时制作这两个图像.

从试飞发射App Clips 测试深度连接. 似乎没有任何合理的方法 测试从你的网站上启动开发。 我通过在我的应用程序的"设置 > 开发者"屏幕中设置本地体验,然后安装应用程序,打开网站,删除应用程序,然后在没有应用程序的情况下安装App Clip,来完成这项工作. 你大部分时间都得和上帝一起去.

您可以使用 CLI 工具生成代码 [在此下载](https://developer.apple.com/download/all/?q=%22app%20clip%22)。 。 。 .

## 常见问题

### @bacons/apple-targets 是什么？

以 Expo 预建创建生成苹果目标

### 如何安装 @bacons/apple-targets？

在项目目录运行 npm install @bacons/apple-targets，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：expo（\>=52）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @bacons/apple-targets 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @bacons/apple-targets 支持 ESM 还是 CommonJS？

以 CommonJS 形式发布，可以直接 require\(\)；在 ES Module 项目中也可以用 import 引入（Node\.js 会把 module\.exports 作为默认导出）。

### @bacons/apple-targets 可以商用吗？

@bacons/apple-targets 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @bacons/apple-targets 的下载量有多少？

根据 npm 官方下载统计，@bacons/apple-targets 在 2026/10/08 – 2026/11/06 这 30 天内被下载 3,224,156 次，日均约 107,472 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @bacons/apple-targets 还在维护吗？

最新版本 5\.0\.0 发布于 2026/07/18（3 个月前）；至今共发布 50 个版本，其中过去 12 个月发布了 11 个；首次发布于 2023/12/04。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

