iOS 中的深度链接和 URL 方案

iOS开发

共 12491字,需浏览 25分钟

 · 2022-11-15

👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇

从 URL 打开应用程序是一项非常强大的 iOS 功能。它将用户吸引到您的应用程序,并可以创建特定功能的快捷方式。本周,我们将深入探讨 iOS 上的深度链接以及如何为您的应用创建 URL 方案。

当我们谈论移动应用程序的深度链接时,它意味着创建一个特定的 URL 来打开移动应用程序。它分为两种格式:

  • 您的应用注册的自定义 URL 方案:scheme://videos
  • 从注册域打开您的应用程序的通用链接:[1]mydomain.com/videos

今天,我们将专注于前者。

我将主要关注 UIKit 实现的代码,但如果您也在寻找它,我还将简要介绍 SwiftUI。

设置 URL 方案

无论您使用的是 SwiftUI 还是 UIKit,为 iOS 设置自定义 URL 方案都是相同的。在 Xcode 中,在您的项目配置下,选择您的目标并导航到Info选项卡。您会URL Types在底部看到一个部分。

深层链接 url 方案

单击+,我可以创建一个新类型。对于标识符,我经常重复使用 app bundle。对于 URL 方案,我建议使用应用程序名称(或缩短)尽可能短。它不应包含任何自定义字符。例如,我将使用deeplink.

而已。该应用程序已准备好识别新 URL,现在我们需要在收到新 URL 时对其进行处理。

SwiftUI 深度链接。

如果你没有任何AppDelegateSceneDelegate文件,这是 SwiftUI 实现的大多数情况,我们没有太多工作要做。

在 App 实现中,我们可以捕获从onOpenURL(perform:)操作中打开的 url。

import SwiftUI

@main
struct DeeplinkSampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    print(url.absoluteString)
                }
        }
    }
}

为了测试它,我可以在模拟器上安装应用程序并从终端应用程序启动给定的 url

xcrun simctl openurl booted "deeplink://test"

很酷!让我们看看 UIKit 的实现有何不同。

UIKit 深层链接

在纸面上,UIKit 或 SwiftUI 不应该对我们处理深度链接的方式产生影响。然而,它主要归结为对于 UIKit 应用程序更常见的一个AppDelegate或一个。SceneDelegate

对于只有 的旧应用AppDelegate,该应用通过以下方法捕获深度链接打开。

extension AppDelegate {

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {

        print(url.absolueString)
        return true
    }
}

如果应用程序可以处理给定的 url,则该函数返回一个布尔值。

对于包含 的较新应用程序,SceneDelegate回调将在那里。重要的是要注意AppDelegate,即使您实现它,也不会调用它。

extension SceneDelegate {
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let firstUrl = URLContexts.first?.url else {
            return
        }

        print(firstUrl.absoluteString)
    }
}

在这个实现中,我们可以注意到我们不再需要返回任何结果。但是,现在传递的参数是 aSet<>而不仅仅是 a URL,它是打开一个或多个 URL。我没有一个用例,我们会拥有更多的 URL,所以我暂时只保留一个。

与之前一样,我们可以在模拟器上安装应用程序并尝试查看是否所有设置正确。我们应该看到打印我们的深层链接 URL。

xcrun simctl openurl booted "deeplink://test"

设置完成后,我们的想法是创建路线以识别和打开正确的屏幕。让我们潜入水中。

深度链接处理程序实现

这个想法很简单,对于给定的链接,我们需要确定我们应该打开什么用户旅程或屏幕。因为它们可以是整个应用程序的许多功能,并且因为我们希望避免大量switch case处理它,所以我们将变得更聪明并且分而治之。

对于这个例子,让我们想象一下我们有一个视频编辑应用程序。它们是 3 个主要选项卡,用于编辑新视频、列出已编辑的视频,然后是包含不同应用程序和用户信息的帐户页面。

我们可以想到三个主要路径

  • deeplink://videos/new- 开始新的视频编辑之旅

  • deeplink://videos- 登陆视频列表选项卡屏幕

  • deeplink://account- 登陆帐户屏幕

首先,我将创建一个深度链接处理程序协议来定义任何新处理程序的最低要求。

protocol DeeplinkHandlerProtocol {
    func canOpenURL(_ url: URL) -> Bool
    func openURL(_ url: URL)
}

我还将定义一个DeeplinkCoordinator将保留在处理程序上并找到正确使用的处理程序。它还像AppDelegatehas 一样返回一个布尔值,因此我们可以在不同的实现中使用。

protocol DeeplinkCoordinatorProtocol {
    @discardableResult
    func handleURL(_ url: URL) -> Bool
}

final class DeeplinkCoordinator {

    let handlers: [DeeplinkHandlerProtocol]

    init(handlers: [DeeplinkHandlerProtocol]) {
        self.handlers = handlers
    }
}

extension DeeplinkCoordinator: DeeplinkCoordinatorProtocol {

    @discardableResult
    func handleURL(_ url: URL) -> Bool{
        guard let handler = handlers.first(where: { $0.canOpenURL(url) }) else {
            return false
        }

        handler.openURL(url)
        return true
    }
}

现在我们可以定义单独的处理程序,每个不同的路径一个。让我们首先从最简单的帐户旅程开始。

final class AccountDeeplinkHandler: DeeplinkHandlerProtocol {

    private weak var rootViewController: UIViewController?
    init(rootViewController: UIViewController?) {
        self.rootViewController = rootViewController
    }

    // MARK: - DeeplinkHandlerProtocol

    func canOpenURL(_ url: URL) -> Bool {
        return url.absoluteString == "deeplink://account"
    }

    func openURL(_ url: URL) {
        guard canOpenURL(url) else {
            return
        }

        // mock the navigation
        let viewController = UIViewController()
        viewController.title = "Account"
        viewController.view.backgroundColor = .yellow
        rootViewController?.present(viewController, animated: true)
    }
}

为了简单起见,我只测试匹配的 url 并导航到正确的屏幕。我还设置了背景颜色,看看我的着陆点是什么。在您的情况下,我们可以只设置正确的UIViewController而不是空的。

我会为不同的视频旅程做同样的事情。

final class VideoDeeplinkHandler: DeeplinkHandlerProtocol {

    private weak var rootViewController: UIViewController?
    init(rootViewController: UIViewController?) {
        self.rootViewController = rootViewController
    }

    // MARK: - DeeplinkHandlerProtocol

    func canOpenURL(_ url: URL) -> Bool {
        return url.absoluteString.hasPrefix("deeplink://videos")
    }
    func openURL(_ url: URL) {
        guard canOpenURL(url) else {
            return
        }

        // mock the navigation
        let viewController = UIViewController()
        switch url.path {
        case "/new":
            viewController.title = "Video Editing"
            viewController.view.backgroundColor = .orange
        default:
            viewController.title = "Video Listing"
            viewController.view.backgroundColor = .cyan
        }

        rootViewController?.present(viewController, animated: true)
    }
}

现在我们可以将它们注入到DeeplinkCoordinator并让它处理正确的路由。我们将有两个变体,第一个用于AppDelegate.

class AppDelegate: UIResponder, UIApplicationDelegate {

    lazy var deeplinkCoordinator: DeeplinkCoordinatorProtocol = {
        return DeeplinkCoordinator(handlers: [
            AccountDeeplinkHandler(rootViewController: self.rootViewController),
            VideoDeeplinkHandler(rootViewController: self.rootViewController)
        ])
    }

    var rootViewController: UIViewController? {
        return window?.rootViewController
    }

    // ...

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
        return deeplinkCoordinator.handleURL(url)
    }
}

第二个为SceneDelegate

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    lazy var deeplinkCoordinator: DeeplinkCoordinatorProtocol = {
        return DeeplinkCoordinator(handlers: [
            AccountDeeplinkHandler(rootViewController: self.rootViewController),
            VideoDeeplinkHandler(rootViewController: self.rootViewController)
        ])
    }()

    var rootViewController: UIViewController? {
        return window?.rootViewController
    }

    // ...

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let firstUrl = URLContexts.first?.url else {
            return
        }

        deeplinkCoordinator.handleURL(firstUrl)
    }

我们可以像目前一样再次测试它,希望能落在正确的屏幕上(期待橙色背景)。

xcrun simctl openurl booted "deeplink://videos/new"

深度链接-ios

总而言之,一旦设置了 URL 方案,我们就定义了一个漏斗来捕获用于打开应用程序的所有深层链接,并利用面向协议的编程来创建处理程序的多个实现,每个特定路径一个。

此实现可针对较新的路径进行扩展,并且可以轻松进行单元测试以确保每个部分都按预期运行。

话虽如此,为了更安全的行为,可能会有一些改进,比如验证完整路径而不是相对路径。仅导航present,但它专注于处理程序而不是转换本身。

在安全说明中,如果您还在深度链接中传递参数,请确保验证预期的类型和值。如果我们不小心,它可能会暴露不同的注入漏洞。

从那里,您应该很好地了解如何使用和处理深度链接来打开您的应用程序并跳转到特定屏幕。此代码可在Github[2]上找到。

参考资料

[1]

通用链接:: https://benoitpasquier.com/universal-links-ios/

[2]

Github: https://github.com/popei69/samples/tree/master/DeeplinkSample


来源:小小小_小朋友

https://juejin.cn/post/7077816330543431710


-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

点击👆卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓↓

浏览 12
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报