這篇有用到的程式碼和觀念和 Day 22 的內容有關係,歡迎也去讀那一篇
本系列列與 NavigationStack 將關的文章如下,也歡迎讀讀
在 iOS 可以透過設定 custom URL schema 的方式,讓 app 外部可以透過這個 schema 開啟 app 。由於設定上幾乎沒有限制,因此在設定的時候必須要注意怎麼樣才不會跟別的 app 相同而相衝,導致在推行活動時因此而導致這一個渠道失效。
格式通常像這樣, myApp 則可以置換成符合 URL schema 或被稱為 "Protocol" 的標準的字串:
myApp://
在 ://
後面就可以像一般的 URL 由 host 和 path 以及 query string 等組合
不太一樣的地方是 host 可以不需要像是一班網站那樣需要有 subdomain, domain 和 TLD 的組合
因此可以簡單到像 RESTful API 那樣資源 (resource) 和識別子 (identifier) 的寫法:
myApp://food/10
把這個網址輸入 Safari 之後,拆開來後可以這樣理解
myApp://
- OS 會透過以這個 protocol 去尋找並開啟 appfood/10
- App 內會收到完整的、包含 protocol 的網址,而 app 內必須自己處理接收到這樣的網址該如何幫 app 跳轉頁面話不多說我們就開始動手吧!
如上圖所示,步驟如下
新增完之後依序輸入這兩個欄位的值即可:
欄位 | 內容 | 說明 |
---|---|---|
Identifier | App ID | 輸入這個 app 的 "Bundle Identifier" |
URL Schemas | 需要被用來開啟的 protocol | 就是前一個段落的 "myApp" 這段,這篇就以 ironman2023 為範例 |
就像這樣,在瀏覽器裡面輸入
ironman2023://
就能夠開啟 app
struct Flavor: Identifiable, Hashable {
let id = UUID().uuidString
let name: String
}
struct ContentView: View {
private let flavors = ["綠豆", "紅豆", "芋粿", "愛玉"].map(Flavor.init)
@State private var path = [Flavor]()
var body: some View {
NavigationStack(path: $path) {
Button {
if let flavor1 = flavors.randomElement(),
let flavor2 = flavors.randomElement() {
path.append(contentsOf: [flavor1, flavor2])
}
} label: {
Text("好手氣")
}
.foregroundColor(.purple)
.padding()
ForEach(flavors) { flavor in
NavigationLink(flavor.name, value: flavor).padding()
}
.navigationDestination(for: Flavor.self) { flavor in
Text("\(flavor.name)")
.navigationTitle(flavor.name)
}
.navigationTitle("點心")
}
}
}
只要在 View 後面接續 onOpenURL 就可以從 perform 的 closure 取得 URL 。
.onOpenURL { url in
print(url.absoluteString)
}
struct ContentView: View {
/* ... */
var body: some View {
NavigationStack(path: $path) { /* ... */ }
.onOpenURL { url in
print(url.absoluteString)
}
}
}
ContentView 是這個 app 的根畫面, onOpenURL 加在最後面。執行 app 之後、從 Safari 觸發,在 Xcode 的 console 就會印出
ironman2023://
和前一天的「好手氣」一樣,我們先來做
接收到 Deep Link 後,取得一個隨機 Flavor 並顯示出來
這時候就像 Day 22 提到的,透過將 flavor 加入 path ,
onOpenURL 內的程式碼如下:
.onOpenURL { url in
if let flavor = flavors.randomElement() {
path.append(flavor)
}
}
執行後從瀏覽器跳轉就會像是這樣
口味總共有四種
["綠豆", "紅豆", "芋粿", "愛玉"]
爲了簡化範例,在不動資料結構來訂這樣的需求
當收到這樣 Deep Link 的時候,顯示相對應的畫面
ironman2023://flavors/{:index}
若是其他網址,單純開啟 app 不需要做任何跳轉
例如,收到
ironman2023://flavors/1
就應該要顯示位於位置陣列位置 [1] 的「紅豆」的頁面
.onOpenURL { url in
guard let host = url.host() else { return }
switch host {
// 正式專案中 "flavors" 通常會用 enum 包裝起來
// 在本篇文章則以純字串呈現
case "flavors":
// pathComponents 中會有斜線,因此先過濾掉
let pathComponents = url.pathComponents.filter { $0 != "/" }
// 取出第一個作為 index ,全部達成條件才繼續執行
guard let first = pathComponents.first,
let index = Int(first),
index < flavors.count else { break }
// 找到目標 flavor 並推入 path
let flavor = flavors[index]
path.append(flavor)
default: break
}
}
以上就是今天的如何在 SwiftUI 中搭配 Deep Link ,試著去解析傳入的網址後,搭配前幾天提到的方式顯示出對應的畫面。
那今天的 SwiftUI 的大大小小就到這邊,明天見!
本篇使用到的 UI 元件和 modifiers 基本上沒有受到版本更新影響。若要在 Xcode 14 等環境下使用也是沒問題的。