SwiftUi配置深层链接
SwiftUi配置深层链接

SwiftUi配置深层链接

在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. nuomi1

    1. 打开手机设置 -> 开发者 -> 通用链接:关联域开发 -> 打开
    2. 打开手机设置 -> 开发者 -> 通用链接:诊断信息 -> URL:添加 domain.com

    这样可以加快调试。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注