苹果目标
一个 Expo 配置插件, 生成本地的 Apple 目标, 如 Widget 或 App Clips, 并将其链接到外部 /ios 目录。 您可以打开 Xcode 并开发虚拟内的目标 expo:targets 文件夹和更改将被保存在 ios 目录。 这种模式使得建筑不属于“土著反应”范围的东西能够同时获得所有的好处 连续的土著一代。 。 。 .
如何使用
这个插件至少需要CocoaPods 1.16.2 (ruby 3.2.0),Xcode 16 (macOS 15 Sequoia),和 Expo SDK + 53.
- 运行
npx create-target以生成苹果目标. - 选择要生成的目标,我建议从
widget(例如:.npx create-target widget) (中文(简体) ). 这将生成根中所需的部件文件/targets目录, 安装@bacons/apple-targets,并在您的工程中添加 Expo 配置插件. - 如果你已经知道你的苹果团队身份, 那就把它放在下面
ios.appleTeamId属性app.json。 您可以在签名和功能标签下的 Xcode 中找到它, 如有必要, 稍后设置它 . - 运行
npx expo prebuild -p ios --clean生成 Xcode 项目. - 您现在可以在 Xcode 中打开您的工程(
xed ios并开发其中的部件expo:targets/<target>文件夹. - 准备建造时,在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 代理技能
这回转船是 代理技能 用于教学 AI 编码代理( Claude Code, Cursor, Codex等) 如何用此插件构建、配置和调试每个支持的 Apple 扩展名和目标类型 。 它包括一个每个目标参考文件与苹果docs,运行时间行为,生产准备的斯威夫特,平台支持,以及Gotchas.
安装到您的代理中 npx skills数字 :
npx skills add EvanBacon/expo-apple-targets
CLI自动检测您安装的代理,并将技能复制到他们的技能目录(例如. .claude/skills/),当您要求代理添加 Apple 扩展时,它会自动加载。 见 医生/技能 细节、更新和删除.
目标配置
目标配置可以是 expo-target.config.js,或 expo-target.config.json 文档.
此文件可以有以下属性 :
/** @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 的函数,并返回一个同步权限和其他值的目标函数:
/** @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 符号模板。当检测到时, SVG 将生成为 .symbolset 内 Assets.xcassets 而不是常规 .imageset。这对本地文件路径和URL都有效.
/** @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",
},
};
在斯威夫特语中,使用符号名作为自定义符号图像:
Image("expo")
您可以轻松使用 :
bun create symbol /path/to/icon.svg
可可罂粟
正在添加文件 pods.rb 在 repo 根中,您可以修改项目的目标设置.
红宝石模块对地产进行全球访问评估 podfile_properties。 。 。 .
例如,以下内容对于在 App Clip 目标中实现 React Native 有用 :
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 结尾处执行 :
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 苹果部件。 。 。 .
/** @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
这些出现在股票单上。 图标应当透明,因为系统将遮盖图标.
/** @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数字 :
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 搜索结果 .
/** @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 构建。此插件将添加目标签名工作所需的权利。 我只用我的柱子谷部件测试过这个端到端.
如果你愿意,你也可以手动签下所有子目标,我会为你点起蜡烛.
我还没有得到 App Clip 代号的完全自动化。 公关欢迎.
建筑部件
我写了一篇博客文章 Expo x 苹果部件。 。 。 .
如果你遇到建造部件的问题, 这可能是因为 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数字 :
{
"expo": {
"ios": {
"entitlements": {
"com.apple.security.application-groups": ["group.bacon.data"]
}
},
"plugins": ["@bacons/apple-targets"]
}
}
第二,定义目标中相同的App组 expo-target.config.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。 。 。 .
在你的博览会应用软件中,你可以设定一个值:
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 直接说 以下是您如何在部件中访问数据的例子 :
let defaults = UserDefaults(suiteName:
// Use the App Group from earlier.
"group.bacon.data"
)
// Access the value you set:
let index = defaults?.string(forKey: "myKey")
更多数据更新
对于更先进的用途,我建议使用下列资源:
- 当应用程序处于背景时更新部件 : 将部件保存到日期。 。 。 .
Xcode 解析
此插件使用我的专有 Xcode 解析库, @bacons/xcode大多是打入的,非常未经测试, 并且可能充满了bugs-尽管如此,它仍然是10x更好的选择.
控制部件
控制部件 是显示在控制中心、Siri建议、锁屏和快捷键上的部件类型.
一般情况下,您会想要将控制部件添加到一个 widget 目标,但是它们可以 添加到任何目标.
你可以加入多种意图,它们应该出现在 [target]/_shared/*.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 重新装入控件
您的应用程序状态的变化可能影响控制显示 。 您可以使用
ExtensionStorage.reloadControls();
自定义图像可以使用,但必须是SF符号. 你可以用 bun create symbol /path/to/icon.svg 或 SF 符号应用程序。将 SVG 添加到目标 images 配置后将自动检测和生成 .symbolset (见 SF 符号) (中文(简体) ).
您可以用控制部件做很多事情,比如启动自定义的UI而不是打开应用程序. 此插件应允许大多数这些东西工作.
缩写
App Clips 利用了 Expo Router 的真正力量, 使您能够链接一个网站和本地应用程序, 以便立即在 iOS 上打开本地应用程序 。 不过他们很难工作.
这是我经验楼的一些笔记 https://pillorvalley.expo.app 页面存档备份,存于互联网档案馆 (打开iOS测试).
先构建应用,再构建网站. 如果情况不对,您随时可以更新网站。 其中包括AASA和元数据.
你也许需要 此 RN 补丁 否则在试飞时会坠毁 或者,你可以在 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) (中文(简体) ). 这里 实例。 。 。 .
值会是 <Apple Team ID>.<App Clip Bundle ID>数字 :
{
"appclips": {
"apps": ["QQ57RJ5UTD.com.evanbacon.pillarvalley.clip"]
}
}
将网站 URL 添加到您的 App Clip 权限( 不是主权限) 。 举个例子 https://pillarvalley.expo.app数字 :
<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 文件 :
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 文件 :
<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。 。 。 . 学多点。这似乎是一条通往 png 图像的绝对路径,即 1200×630 b. 涉及各层面的以此为基础) (中文(简体) ).
<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 工具生成代码 在此下载。 。 。 .