iOS 中的深度鏈接和 URL 方案
從 URL 打開應(yīng)用程序是一項(xiàng)非常強(qiáng)大的 iOS 功能。它將用戶吸引到您的應(yīng)用程序,并可以創(chuàng)建特定功能的快捷方式。本周,我們將深入探討 iOS 上的深度鏈接以及如何為您的應(yīng)用創(chuàng)建 URL 方案。
當(dāng)我們談?wù)撘苿?dòng)應(yīng)用程序的深度鏈接時(shí),它意味著創(chuàng)建一個(gè)特定的 URL 來打開移動(dòng)應(yīng)用程序。它分為兩種格式:
-
您的應(yīng)用注冊的自定義 URL 方案: scheme://videos -
從注冊域打開您的應(yīng)用程序的通用鏈接:[1] mydomain.com/videos
今天,我們將專注于前者。
我將主要關(guān)注 UIKit 實(shí)現(xiàn)的代碼,但如果您也在尋找它,我還將簡要介紹 SwiftUI。
設(shè)置 URL 方案
無論您使用的是 SwiftUI 還是 UIKit,為 iOS 設(shè)置自定義 URL 方案都是相同的。在 Xcode 中,在您的項(xiàng)目配置下,選擇您的目標(biāo)并導(dǎo)航到Info選項(xiàng)卡。您會(huì)URL Types在底部看到一個(gè)部分。
單擊+,我可以創(chuàng)建一個(gè)新類型。對(duì)于標(biāo)識(shí)符,我經(jīng)常重復(fù)使用 app bundle。對(duì)于 URL 方案,我建議使用應(yīng)用程序名稱(或縮短)盡可能短。它不應(yīng)包含任何自定義字符。例如,我將使用deeplink.
而已。該應(yīng)用程序已準(zhǔn)備好識(shí)別新 URL,現(xiàn)在我們需要在收到新 URL 時(shí)對(duì)其進(jìn)行處理。
SwiftUI 深度鏈接。
如果你沒有任何AppDelegate和SceneDelegate文件,這是 SwiftUI 實(shí)現(xiàn)的大多數(shù)情況,我們沒有太多工作要做。
在 App 實(shí)現(xiàn)中,我們可以捕獲從onOpenURL(perform:)操作中打開的 url。
import SwiftUI
@main
struct DeeplinkSampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
print(url.absoluteString)
}
}
}
}
為了測試它,我可以在模擬器上安裝應(yīng)用程序并從終端應(yīng)用程序啟動(dòng)給定的 url
xcrun simctl openurl booted "deeplink://test"
很酷!讓我們看看 UIKit 的實(shí)現(xiàn)有何不同。
UIKit 深層鏈接
在紙面上,UIKit 或 SwiftUI 不應(yīng)該對(duì)我們處理深度鏈接的方式產(chǎn)生影響。然而,它主要?dú)w結(jié)為對(duì)于 UIKit 應(yīng)用程序更常見的一個(gè)AppDelegate或一個(gè)。SceneDelegate
對(duì)于只有 的舊應(yīng)用AppDelegate,該應(yīng)用通過以下方法捕獲深度鏈接打開。
extension AppDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
print(url.absolueString)
return true
}
}
如果應(yīng)用程序可以處理給定的 url,則該函數(shù)返回一個(gè)布爾值。
對(duì)于包含 的較新應(yīng)用程序,SceneDelegate回調(diào)將在那里。重要的是要注意AppDelegate,即使您實(shí)現(xiàn)它,也不會(huì)調(diào)用它。
extension SceneDelegate {
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let firstUrl = URLContexts.first?.url else {
return
}
print(firstUrl.absoluteString)
}
}
在這個(gè)實(shí)現(xiàn)中,我們可以注意到我們不再需要返回任何結(jié)果。但是,現(xiàn)在傳遞的參數(shù)是 aSet<>而不僅僅是 a URL,它是打開一個(gè)或多個(gè) URL。我沒有一個(gè)用例,我們會(huì)擁有更多的 URL,所以我暫時(shí)只保留一個(gè)。
與之前一樣,我們可以在模擬器上安裝應(yīng)用程序并嘗試查看是否所有設(shè)置正確。我們應(yīng)該看到打印我們的深層鏈接 URL。
xcrun simctl openurl booted "deeplink://test"
設(shè)置完成后,我們的想法是創(chuàng)建路線以識(shí)別和打開正確的屏幕。讓我們潛入水中。
深度鏈接處理程序?qū)崿F(xiàn)
這個(gè)想法很簡單,對(duì)于給定的鏈接,我們需要確定我們應(yīng)該打開什么用戶旅程或屏幕。因?yàn)樗鼈兛梢允钦麄€(gè)應(yīng)用程序的許多功能,并且因?yàn)槲覀兿M苊獯罅?code style="font-size: 14px;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">switch case處理它,所以我們將變得更聰明并且分而治之。
對(duì)于這個(gè)例子,讓我們想象一下我們有一個(gè)視頻編輯應(yīng)用程序。它們是 3 個(gè)主要選項(xiàng)卡,用于編輯新視頻、列出已編輯的視頻,然后是包含不同應(yīng)用程序和用戶信息的帳戶頁面。
我們可以想到三個(gè)主要路徑
-
deeplink://videos/new- 開始新的視頻編輯之旅 -
deeplink://videos- 登陸視頻列表選項(xiàng)卡屏幕 -
deeplink://account- 登陸帳戶屏幕
首先,我將創(chuàng)建一個(gè)深度鏈接處理程序協(xié)議來定義任何新處理程序的最低要求。
protocol DeeplinkHandlerProtocol {
func canOpenURL(_ url: URL) -> Bool
func openURL(_ url: URL)
}
我還將定義一個(gè)DeeplinkCoordinator將保留在處理程序上并找到正確使用的處理程序。它還像AppDelegatehas 一樣返回一個(gè)布爾值,因此我們可以在不同的實(shí)現(xiàn)中使用。
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
}
}
現(xiàn)在我們可以定義單獨(dú)的處理程序,每個(gè)不同的路徑一個(gè)。讓我們首先從最簡單的帳戶旅程開始。
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 并導(dǎo)航到正確的屏幕。我還設(shè)置了背景顏色,看看我的著陸點(diǎn)是什么。在您的情況下,我們可以只設(shè)置正確的UIViewController而不是空的。
我會(huì)為不同的視頻旅程做同樣的事情。
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)
}
}
現(xiàn)在我們可以將它們注入到DeeplinkCoordinator并讓它處理正確的路由。我們將有兩個(gè)變體,第一個(gè)用于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)
}
}
第二個(gè)為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"
總而言之,一旦設(shè)置了 URL 方案,我們就定義了一個(gè)漏斗來捕獲用于打開應(yīng)用程序的所有深層鏈接,并利用面向協(xié)議的編程來創(chuàng)建處理程序的多個(gè)實(shí)現(xiàn),每個(gè)特定路徑一個(gè)。
此實(shí)現(xiàn)可針對(duì)較新的路徑進(jìn)行擴(kuò)展,并且可以輕松進(jìn)行單元測試以確保每個(gè)部分都按預(yù)期運(yùn)行。
話雖如此,為了更安全的行為,可能會(huì)有一些改進(jìn),比如驗(yàn)證完整路徑而不是相對(duì)路徑。僅導(dǎo)航present,但它專注于處理程序而不是轉(zhuǎn)換本身。
在安全說明中,如果您還在深度鏈接中傳遞參數(shù),請確保驗(yàn)證預(yù)期的類型和值。如果我們不小心,它可能會(huì)暴露不同的注入漏洞。
從那里,您應(yīng)該很好地了解如何使用和處理深度鏈接來打開您的應(yīng)用程序并跳轉(zhuǎn)到特定屏幕。此代碼可在Github[2]上找到。
參考資料
通用鏈接:: https://benoitpasquier.com/universal-links-ios/
[2]Github: https://github.com/popei69/samples/tree/master/DeeplinkSample
來源:小小小_小朋友
https://juejin.cn/post/7077816330543431710
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取
