在App Store的App内活动中,发现需要配置“活动深层链接”才能提交审核。

什么是深层链接?
深层链接用于指向应用内的具体页面,确保用户点击活动时能够直接跳转到相应的内容,而不仅仅打开应用首页。

在这里,当用户在 App Store 上看到 App 内活动(比如“动画不停歇”),点击后,他们不会只是打开应用,而是会直接跳转到该功能所在的页面,比如设置动画循环的界面。
深层链接接通常是 URL Scheme(URL 方案) 或 Universal Link(通用链接),格式类似于:
URL Scheme :myapp://animation-loop
Universal Link :https://fangjunyu.com/animation-loop
如果应用支持 Universal Link,建议使用 HTTPS 地址,否则可以使用自定义的 URL Scheme。
配置深层链接
在iOS应用中配置深层链接(Deep Link),可以选择URL Scheme 或 Universal Link。
URL Scheme
配置URL Sheme
1、打开 Xcode,进入 Info.plist 文件。

2、找到 URL Types,如果没有就手动添加:

新增的 URL Types默认只有 URL identifier 字段,还需要添加一个URL Schemes字段。

URL Identifier:填一个唯一标识(如 com.fangjunyu.piglet)。
URL Schemes:填 Scheme(格式),例如 Banklet(我的存钱罐的名称)

最终的URL Sheme的格式是 Banklet://。
3、运行 App,确保它可以注册这个 Scheme。
4、在SwiftUI的@main入口文件或NavigationLink逻辑里处理深层链接跳转。
例如我在应用的主视图中添加onOpenURL:
VStack { ... }
.onOpenURL { url in
if url.scheme == "Banklet", url.host == "animation-loop" {
// 打开设置视图
withAnimation(.easeInOut(duration: 1)) {
isDisplaySettings.toggle()
}
}
}
当我从外部点击 Banklet://animation-loop时,会打开应用并进入主视图,主视图的onOpenURL会检测到点击的链接内容,并根据scheme和host执行相关的代码。
例如这里会根据链接,自动打开设置视图。
测试URL Sheme
在iPhone备忘录中添加:
Banklet://animation-loop

点击深层链接,就会自动跳转到应用并打开设置的视图。
也可以在iOS的快捷指令App中,创建新的快捷指令,添加“打开URL”。
输入 Banklet://animation-loop ,运行该快捷指令,打开Banklet。

传递URL事件
在实际的应用中,可能复杂一些。例如,我想要打开应用中动画视图。
但是打开动画视图需要多个步骤:
1、在主视图弹出设置Sheet视图。
2、在设置Sheet视图中打开通用视图的NavigationLink链接。
3、在通用视图中打开动画视图的NavigationLink链接。
在这里涉及到Sheet和NavigationLink两种打开方式。

如果想要在多个视图中添加onOpenURL,则无法实现这一效果,也可能是我测试错了。
当我测试主视图、设置视图和通用视图中添加onOpenURL时,onOpenURL只会在主视图打开设置视图,设置用视图中的onOpenURL无法执行代码。
Home {}
.onOpenURL { url in
if url.scheme == "Banklet", url.host == "animation-loop" {
// 打开设置视图
withAnimation(.easeInOut(duration: 1)) {
isDisplaySettings.toggle()
}
}
}
Setting {}
.onOpenURL { url in
if url.scheme == "Banklet", url.host == "animation-loop" {
// 打开通用视图
withAnimation(.easeInOut(duration: 1)) {
isDisplayGeneric= true
}
}
}

经过查询了解到onOpenURL只会在当前可见视图触发,而设置视图是在打开主视图之后打开的。所以onOpenURL事件在主视图触发,设置视图还未加载,自然不会监听到这个事件。
因此,可以考虑使用@EnvironmentObject或@AppStorage传递URL事件。
方法1、使用@AppStorage
适用于App需要在视图加载后依然可以响应URL事件的情况。
1、主视图存储URL事件
import SwiftUI
struct HomeView: View {
@AppStorage("pendingDeepLink") private var pendingDeepLink: String?
var body: some View {
NavigationView {
VStack {
Text("Home View")
}
.onOpenURL { url in
if url.scheme == "Banklet", url.host == "animation-loop" {
// 存储 deep link 信息,等待 SettingView 处理
pendingDeepLink = "animation-loop"
// 打开设置视图
isDisplaySettings.toggle()
}
}
}
}
}
这里pendingDeepLink存储传递的host值,进入下一个视图时还能检测到这个值。
2、设置视图监听pendingDeepLink并执行跳转
import SwiftUI
struct SettingView: View {
@AppStorage("pendingDeepLink") private var pendingDeepLink: String?
@State private var isDisplayGeneric = false // 显示通用视图
var body: some View {
VStack {
NavigationLink(destination: MainInterfaceAnimationView(), isActive: $isDisplayGeneric) {
Text("进入动画页面")
}
}
.onAppear {
// 进入 SettingView 时检查是否有 deep link 需要处理
if pendingDeepLink == "animation-loop" {
withAnimation(.easeInOut(duration: 1)) {
isDisplayGeneric = true
}
}
}
}
}
设置视图一旦加载,就检查pendingDeepLink 是否等于 “animation-loop”。
如果匹配,则执行 navigateToAnimation = true,触发 NavigationLink 进入 MainInterfaceAnimationView。
3、设置通用视图并跳转到动画视图
import SwiftUI
struct GeneralView: View {
@AppStorage("pendingDeepLink") private var pendingDeepLink: String?
@State private var navigateToAnimation = false
var body: some View {
VStack {
NavigationLink(destination: MainInterfaceAnimationView(), isActive: $navigateToAnimation) {
Text("进入动画页面")
}
}
.onAppear {
// 进入 SettingView 时检查是否有 deep link 需要处理
if pendingDeepLink == "animation-loop" {
withAnimation(.easeInOut(duration: 1)) {
navigateToAnimation = true
}
// 处理后清除 deep link,防止重复执行
pendingDeepLink = nil
}
}
}
}
通用视图和设置视图一样,区别在于当进入通用视图时,执行跳转命令后,会清除 deep link,防止重复执行。
最后在通用视图中,打开动画视图,完成整个URL事件的传递。

整个逻辑是在主视图中监听传递的host值,然后存储在@AppStore中,每个被打开的视图都会根据@AppStore中的值处理每一步的打开代码。
还需要注意,在打开NavigationLink时,使用的是isActive绑定一个变量,在onAppear中设置绑定变量为true,从而实现NavigaitonLink的跳转。
NavigationLink(destination: some View,isActive: $var){
// 显示内容
}
方法2、使用@EnvironmentObject
如果不需要持久化存储Deep Link事件,可以使用@EnvironmentObject。
1、创建 DeepLinkManager
import SwiftUI
class DeepLinkManager: ObservableObject {
@Published var deepLink: String?
}
2、在 @main 入口处提供 DeepLinkManager
@main
struct BankletApp: App {
@StateObject var deepLinkManager = DeepLinkManager()
var body: some Scene {
WindowGroup {
HomeView()
.environmentObject(deepLinkManager)
}
}
}
3、在主视图监听URL并存入deepLink
import SwiftUI
struct HomeView: View {
@EnvironmentObject var deepLinkManager: DeepLinkManager
var body: some View {
NavigationView {
VStack {
Text("Home View")
}
.onOpenURL { url in
if url.scheme == "Banklet", url.host == "animation-loop" {
withAnimation(.easeInOut(duration: 1)) {
deepLinkManager.deepLink = "animation-loop"
// 打开设置视图
isDisplaySettings.toggle()
}
}
}
}
}
}
4、在设置视图中读取deepLinkManager
import SwiftUI
struct SettingView: View {
@EnvironmentObject var deepLinkManager: DeepLinkManager
@State private var isDisplayGeneric = false // 显示通用视图
var body: some View {
VStack {
NavigationLink(destination: MainInterfaceAnimationView(), isActive: $isDisplayGeneric) {
Text("进入动画页面")
}
}
.onAppear {
if deepLinkManager.deepLink == "animation-loop" {
withAnimation(.easeInOut(duration: 1)) {
isDisplayGeneric = true
}
}
}
}
}
在后面的通用视图中与设置视图的代码基本一致,只是通用视图在处理完成后,需要清除 deepLink:
// 处理后清除 deep link
deepLinkManager.deepLink = nil
小结
以上两种方法,不论是@AppStorage还是@EnvironmentObject,都可以实现对于URL事件的传递。
Universal Link
如果希望网页点击后能直接跳转 App(而不是打开网页),则需要使用 Apple 的 Universal Links 机制。
配置 Apple App Site Association(AASA)文件
在服务器上创建一个JSON文件,放在 HTTPS 的 .well-known 目录下,比如:
https://fangjunyu.com/.well-known/apple-app-site-association
示例 JSON 文件:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.fangjunyu.piglet", // TEAMID.BundleID
"paths": [ "/animation-loop" ] // 允许的路径
}
]
}
}
关于服务器配置深层链接文件的相关教程,目前已经整理到《服务器配置iOS深层链接》文章中。
TEAMID 是 Apple 开发者团队 ID(可在 App Store Connect 找到)。

com.yourcompany.Banklet 是应用 Bundle ID。
paths 是允许的路径,例如 /animation-loop。
在 Xcode 配置 Associated Domains
1、在 Xcode > Signing & Capabilities 里添加 Associated Domains。

2、添加:
applinks:yourdomain.com

3、SwiftUI中设置onOpenURL处理Universal Link:
import SwiftUI
@main
struct YourApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// 定义处理 Universal Link 的函数
print("Universal Link opened: \(url)")
}
}
}
}
在 Web 页面跳转
当从应用中访问超链接:
https://fangjunyu.com/Settings
如果App已安装,它会直接打开 App;如果未安装,会跳转到网页。

扩展知识
在使用Universal Link时,当我修改.well-known/apple-app-site-association文件的内容:
"paths": [ "/animation-loop" ] // 修改前
"paths": [ "/Settings" ] // 修改后
重新访问链接:
https://fangjunyu.com/Settings
无法通过修改后的链接实现应用的跳转,即使我使用以下几种方法尝试:
1)删除iPhone浏览器缓存;
2)等待apple-app-site-association文件同步为最新的内容;
3)重启iPhone;
4)Xcode覆盖安装应用;
5)Xcode卸载重装应用;
6)还原iPhone的网络设置;
7)重启服务器的Nginx服务。
以上方法都无法实现使用修改后的链接跳转到应用。
我通过查询了解,iOS通常每隔24-48小时会重新获取apple-app-site-association,如果上述方法你也无法处理你的问题,可以确定apple-app-site-association内容无误后,等待一段时间,然后重新校验。
我的猜测是apple-app-site-association与浏览器缓存或iPhone应用无关,可能是存储在iOS手机上。
具体定论还需要更长的时间排查才行。
相关文章
1、Xcode项目Info.plist文件位置:https://fangjunyu.com/2024/12/08/xcode%e9%a1%b9%e7%9b%aeinfo-plist%e6%96%87%e4%bb%b6%e4%bd%8d%e7%bd%ae/
2、Allowing apps and websites to link to your content:https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content
3、服务器配置iOS深层链接:https://fangjunyu.com/2025/02/08/%e6%9c%8d%e5%8a%a1%e5%99%a8%e9%85%8d%e7%bd%aeios%e6%b7%b1%e5%b1%82%e9%93%be%e6%8e%a5/
1. 打开手机设置 -> 开发者 -> 通用链接:关联域开发 -> 打开
2. 打开手机设置 -> 开发者 -> 通用链接:诊断信息 -> URL:添加 domain.com
这样可以加快调试。