行動 SDK深層連結
iOS
AdStage DeepLink 整合指南(iOS)
目錄
概覽
AdStage DeepLink SDK 提供以下功能:
- 即時深層連結:透過 URL Scheme、Universal Links 立即處理
- 延遲深層連結:應用程式安裝後首次啟動時自動還原
- 動態深層連結建立:透過伺服器 API 建立可追蹤的連結
- 歸因追蹤:以 UTM 參數為基礎的行銷分析
專案設定
CocoaPods 設定
Podfile
platform :ios, '15.0'
target 'YourApp' do
use_frameworks!
# AdStage SDK
pod 'AdapterAdStage', '3.0.11'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
end
end
end安裝:
pod installInfo.plist 設定
1. URL Scheme 設定
於 Info.plist 中新增:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.example.myapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>2. Universal Links 設定
於 Info.plist 中新增:
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:go.myapp.com</string>
<string>applinks:go.adstage.net</string>
</array>Xcode 設定:
- 選擇 Signing & Capabilities 分頁
- 點擊 + Capability
- 新增 Associated Domains
- 新增網域:
applinks:go.myapp.comapplinks:go.adstage.net
3. Apple App Site Association 檔案
部署至伺服器:https://go.myapp.com/.well-known/apple-app-site-association
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAM_ID.com.example.myapp",
"paths": ["*"]
}
]
}
}注意事項:
- 沒有副檔名(請勿加上
.json) - Content-Type:
application/json - 必須使用 HTTPS
- 置於根路徑或
.well-known資料夾
4. 必要權限設定
<!-- 網路通訊 -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
<!-- 背景模式(選用) -->
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>AppDelegate 設定
Swift - AppDelegate.swift
import UIKit
import AdapterAdStage
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
// MARK: - Application Lifecycle
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 1. 初始化 AdStage SDK
initializeAdStage()
// 2. 設定深層連結監聽器
setupDeepLinkListener()
return true
}
// MARK: - AdStage Initialization
private func initializeAdStage() {
AdStageManager.shared.initialize(
apiKey: "your-api-key-here",
serverUrl: "https://api.adstage.app" // 選用
)
print("✅ AdStage SDK 초기화 완료")
}
private func setupDeepLinkListener() {
// 設定整合式深層連結監聽器
DeepLinkManager.shared.setDeepLinkListener { [weak self] deepLinkData in
guard let self = self else { return }
print("""
📱 딥링크 수신:
- Short Path: \(deepLinkData.shortPath)
- Link ID: \(deepLinkData.linkId)
- Source: \(deepLinkData.source.description)
- Parameters: \(deepLinkData.parameters)
""")
// 處理深層連結
self.handleDeepLink(deepLinkData)
}
}
// MARK: - Deep Link Handling
private func handleDeepLink(_ data: DeepLinkData) {
// 依來源分別處理
switch data.source {
case .realtime:
print("🔗 실시간 딥링크 처리")
handleRealtimeDeepLink(data)
case .install:
print("📦 디퍼드 딥링크 처리 (앱 설치 후 첫 실행)")
handleDeferredDeepLink(data)
case .unknown:
print("❓ 알 수 없는 딥링크 소스")
}
}
private func handleRealtimeDeepLink(_ data: DeepLinkData) {
// 依參數切換畫面
if let campaign = data.parameters["campaign"] {
navigateToCampaign(campaign)
} else if let promo = data.parameters["promo"] {
navigateToPromotion(promo)
} else {
navigateToHome()
}
}
private func handleDeferredDeepLink(_ data: DeepLinkData) {
// 於導覽流程後處理,或立即處理
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.handleRealtimeDeepLink(data)
}
}
// MARK: - Navigation
private func navigateToCampaign(_ campaign: String) {
print("🎯 캠페인 화면으로 이동: \(campaign)")
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let campaignVC = storyboard.instantiateViewController(
withIdentifier: "CampaignViewController"
) as? CampaignViewController {
campaignVC.campaignId = campaign
if let navController = self.window?.rootViewController as? UINavigationController {
navController.pushViewController(campaignVC, animated: true)
}
}
}
}
private func navigateToPromotion(_ promo: String) {
print("🎁 프로모션 적용: \(promo)")
// 推廣處理邏輯
}
private func navigateToHome() {
print("🏠 홈 화면으로 이동")
// 前往首頁
}
// MARK: - URL Scheme Deep Link (iOS 8+)
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
print("📲 URL Scheme 딥링크 수신: \(url)")
// 交由 AdStage 處理
let handled = DeepLinkManager.shared.handleDeepLink(url)
if handled {
print("✅ AdStage가 딥링크를 처리했습니다")
return true
}
// 處理其他 URL Scheme(例如 OAuth 回呼)
if url.scheme == "myapp" {
// 自訂處理
return handleCustomScheme(url)
}
return false
}
// MARK: - Universal Links (iOS 9+)
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
// 確認是否為 Universal Link
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
print("🌐 Universal Link 수신: \(url)")
// 交由 AdStage 處理
let handled = DeepLinkManager.shared.handleUniversalLink(userActivity)
if handled {
print("✅ AdStage가 Universal Link를 처리했습니다")
return true
}
// 處理其他 Universal Link
return handleCustomUniversalLink(url)
}
// MARK: - Custom Handlers
private func handleCustomScheme(_ url: URL) -> Bool {
print("🔧 커스텀 URL Scheme 처리: \(url)")
// 處理 OAuth、付款回呼等
return false
}
private func handleCustomUniversalLink(_ url: URL) -> Bool {
print("🔧 커스텀 Universal Link 처리: \(url)")
// 處理一般網頁連結
return false
}
}SwiftUI - App 結構
import SwiftUI
import AdapterAdStage
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// 處理 URL Scheme
print("📲 URL 수신: \(url)")
_ = DeepLinkManager.shared.handleDeepLink(url)
}
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
// 初始化 AdStage
AdStageManager.shared.initialize(
apiKey: "your-api-key-here",
serverUrl: "https://api.adstage.app"
)
// 設定深層連結監聽器
setupDeepLinkListener()
return true
}
private func setupDeepLinkListener() {
DeepLinkManager.shared.setDeepLinkListener { deepLinkData in
print("📱 딥링크 수신: \(deepLinkData.shortPath)")
// 處理邏輯
}
}
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
// 處理 Universal Link
return DeepLinkManager.shared.handleUniversalLink(userActivity)
}
}深層連結接收處理
1. 即時深層連結處理
import AdapterAdStage
class DeepLinkHandler {
static let shared = DeepLinkHandler()
func setupListener() {
DeepLinkManager.shared.setDeepLinkListener { [weak self] deepLinkData in
guard let self = self else { return }
// 取出深層連結資料
let shortPath = deepLinkData.shortPath
let parameters = deepLinkData.parameters
let source = deepLinkData.source
print("""
✅ 딥링크 수신 완료
- Short Path: \(shortPath)
- Link ID: \(deepLinkData.linkId)
- Source: \(source.description)
- Event Type: \(deepLinkData.eventType)
""")
// 取出 UTM 參數
let utmParams = deepLinkData.getUtmParameters()
print("📊 UTM Parameters: \(utmParams)")
// 取出自訂參數
let customParams = deepLinkData.getCustomParameters()
print("🔧 Custom Parameters: \(customParams)")
// 處理商業邏輯
self.processDeepLink(deepLinkData)
}
}
private func processDeepLink(_ data: DeepLinkData) {
// 依參數分支處理
if let campaign = data.getParameter("campaign") {
handleCampaignDeepLink(campaign, data: data)
} else if let productId = data.getParameter("product_id") {
handleProductDeepLink(productId, data: data)
} else if let promo = data.getParameter("promo") {
handlePromoDeepLink(promo, data: data)
} else {
handleDefaultDeepLink(data)
}
}
private func handleCampaignDeepLink(_ campaign: String, data: DeepLinkData) {
print("🎯 캠페인 딥링크: \(campaign)")
DispatchQueue.main.async {
// 前往廣告活動畫面
NotificationCenter.default.post(
name: .navigateToCampaign,
object: nil,
userInfo: ["campaign": campaign, "data": data]
)
}
}
private func handleProductDeepLink(_ productId: String, data: DeepLinkData) {
print("🛍️ 상품 딥링크: \(productId)")
DispatchQueue.main.async {
NotificationCenter.default.post(
name: .navigateToProduct,
object: nil,
userInfo: ["productId": productId]
)
}
}
private func handlePromoDeepLink(_ promo: String, data: DeepLinkData) {
print("🎁 프로모션 딥링크: \(promo)")
// 自動套用推廣碼
applyPromoCode(promo)
}
private func handleDefaultDeepLink(_ data: DeepLinkData) {
print("📋 기본 딥링크 처리")
DispatchQueue.main.async {
// 前往首頁
NotificationCenter.default.post(name: .navigateToHome, object: nil)
}
}
private func applyPromoCode(_ code: String) {
// 推廣套用邏輯
print("프로모션 코드 적용: \(code)")
}
}
// 定義 Notification 名稱
extension Notification.Name {
static let navigateToCampaign = Notification.Name("navigateToCampaign")
static let navigateToProduct = Notification.Name("navigateToProduct")
static let navigateToHome = Notification.Name("navigateToHome")
}2. 延遲深層連結(自動處理)
// 延遲深層連結會在 AdStageManager.initialize() 時自動處理。
// 無須另行實作!
// 初始化時會自動:
// 1. 收集裝置指紋
// 2. 向伺服器發出比對請求
// 3. 若有已儲存的深層連結,則自動呼叫 onDeepLinkReceived
// 延遲深層連結未提供手動觸發 API,會在 initialize() 之後自動處理。
// 比對結果會透過上方註冊的 setDeepLinkListener 回呼傳遞。3. 依來源處理
func handleDeepLink(_ data: DeepLinkData) {
switch data.source {
case .realtime:
print("🔗 실시간 딥링크 - 앱 실행 중 수신")
// 可立即切換畫面
navigateImmediately(data)
case .install:
print("📦 디퍼드 딥링크 - 앱 설치 후 첫 실행")
// 於導覽流程後處理,或延後處理
showOnboardingThenNavigate(data)
case .unknown:
print("❓ 알 수 없는 소스")
handleAsDefault(data)
}
}
private func showOnboardingThenNavigate(_ data: DeepLinkData) {
// 導覽流程完成後處理
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
self?.navigateImmediately(data)
}
}建立深層連結
1. Builder 模式
import AdapterAdStage
class DeepLinkCreator {
// 建立簡單的深層連結
func createSimpleDeepLink() {
AdStageManager.shared.createDeepLinkBuilder()
.setName("여름 프로모션")
.setDescription("2024년 여름 시즌 프로모션")
.setChannel("google-ads")
.setCampaign("summer2024")
.addParameter(key: "promo", value: "SUMMER20")
.create { [weak self] deepLinkInfo, error in
if let error = error {
print("❌ 딥링크 생성 실패: \(error.localizedDescription)")
return
}
guard let info = deepLinkInfo else {
print("❌ 딥링크 정보 없음")
return
}
print("""
✅ 딥링크 생성 완료
- Short URL: \(info.shortUrl)
- Short Path: \(info.shortPath)
- Link ID: \(info.linkId)
""")
// 分享建立好的 URL
self?.shareDeepLink(info.shortUrl)
}
}
// 包含全部選項
func createFullDeepLink() {
AdStageManager.shared.createDeepLinkBuilder()
// 基本資訊
.setName("겨울 프로모션")
.setDescription("2024-2025 겨울 시즌 프로모션")
.setShortPath("WINTER24") // 使用者指定的路徑
// 追蹤參數
.setChannel("facebook-ads")
.setSubChannel("instagram")
.setCampaign("winter2024")
.setAdGroup("fashion-lovers")
.setCreative("banner-001")
.setContent("hero-image")
.setKeyword("winter-sale")
// Android 設定
.setAndroidConfig(
packageName: "com.example.myapp",
appScheme: "myapp://promo/winter",
webUrl: "https://example.com/promo/winter"
)
// iOS 設定
.setIosConfig(
appStoreId: "123456789",
appScheme: "myapp://promo/winter",
webUrl: "https://example.com/promo/winter"
)
// 網頁設定
.setWebConfig(webUrl: "https://example.com/promo/winter")
// 自訂參數
.addParameter(key: "discount", value: "30")
.addParameter(key: "promoCode", value: "WINTER30")
.addParameter(key: "validUntil", value: "2025-03-31")
.create { deepLinkInfo, error in
if let error = error {
print("❌ 에러: \(error.localizedDescription)")
return
}
guard let info = deepLinkInfo else { return }
print("✅ Short URL: \(info.shortUrl)")
}
}
// 一次設定多個參數
func createDeepLinkWithMultipleParams() {
let parameters: [String: String] = [
"campaign": "spring2024",
"discount": "15",
"promoCode": "SPRING15",
"source": "email"
]
AdStageManager.shared.createDeepLinkBuilder()
.setName("봄 프로모션")
.setChannel("email-marketing")
.setParameters(parameters)
.create { deepLinkInfo, error in
// 處理
}
}
// 分享功能
private func shareDeepLink(_ url: String) {
DispatchQueue.main.async {
let text = """
🎁 특별 프로모션 초대!
이 링크를 통해 가입하면 5,000원 할인 쿠폰을 드립니다.
\(url)
"""
let activityVC = UIActivityViewController(
activityItems: [text],
applicationActivities: nil
)
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = windowScene.windows.first?.rootViewController {
rootVC.present(activityVC, animated: true)
}
}
}
}2. async/await 方式
class AsyncDeepLinkCreator {
func createDeepLink() async {
do {
let builder = AdStageManager.shared.createDeepLinkBuilder()
.setName("신규 가입 이벤트")
.setChannel("referral")
.setCampaign("invite-friend")
.addParameter(key: "referrer", value: "USER123")
.addParameter(key: "bonus", value: "5000")
let info = try await builder.createAsync()
print("✅ 딥링크 생성 완료: \(info.shortUrl)")
// 更新 UI
await MainActor.run {
self.updateUI(with: info.shortUrl)
}
} catch {
print("❌ 딥링크 생성 실패: \(error.localizedDescription)")
await MainActor.run {
self.showError(error)
}
}
}
@MainActor
private func updateUI(with url: String) {
// 更新 UI
}
@MainActor
private func showError(_ error: Error) {
// 顯示錯誤
}
}3. Objective-C 相容方式
createDeepLinkBuilder() 與 DeepLinkBuilder 的鏈式方法、create(completion:)
皆已透過 @objc 公開,因此在 Objective-C 中也可直接使用。
你可以建立一個簡單的包裝器,讓回呼只取得 Short URL 字串。
// 可在 Objective-C 中使用的簡單包裝器
@objc class ObjCCompatibleDeepLinkCreator: NSObject {
@objc func createSimpleDeepLink(name: String, completion: @escaping (String?, Error?) -> Void) {
AdStageManager.shared.createDeepLinkBuilder()
.setName(name)
.create { info, error in
completion(info?.shortUrl, error)
}
}
@objc func createDeepLinkWithDescription(
name: String,
description: String,
completion: @escaping (String?, Error?) -> Void
) {
AdStageManager.shared.createDeepLinkBuilder()
.setName(name)
.setDescription(description)
.create { info, error in
completion(info?.shortUrl, error)
}
}
}進階功能
1. 設定全域使用者屬性
以 UserAttributes 設定使用者資訊後,之後送出的事件會自動包含這些資訊。
setUserAttributes(_:) 以 UserAttributes 物件作為引數,所有欄位皆為選用值。
// 登入時
func onUserLogin(userProfile: UserProfile) {
let userAttributes = UserAttributes(
gender: userProfile.gender,
country: userProfile.country,
city: userProfile.city,
age: "\(userProfile.age)",
language: Locale.current.languageCode ?? "en"
)
AdStageManager.shared.setUserAttributes(userAttributes)
print("✅ 사용자 정보 설정 완료")
}
// 登出時(以空屬性重設)
func onUserLogout() {
AdStageManager.shared.setUserAttributes(UserAttributes())
print("🚪 사용자 정보 초기화")
}參考:裝置資訊(平台、機型、應用程式版本、OS 版本、IDFV/IDFA、ATT 狀態等) 由 SDK 在送出事件時自動收集並包含,因此無須另行設定。
疑難排解
1. Universal Link 無法運作
檢查清單:
- 確認 Associated Domains 設定
- 確認已部署 apple-app-site-association 檔案
- 確認使用 HTTPS
- 確認 Team ID 與 Bundle ID 一致
驗證方式:
# 1. 確認檔案是否可存取
curl https://go.myapp.com/.well-known/apple-app-site-association
# 2. 確認 Apple CDN 快取
curl https://app-site-association.cdn-apple.com/a/v1/go.myapp.com偵錯:
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
print("""
Universal Link Debug:
- Activity Type: \(userActivity.activityType)
- Webpage URL: \(userActivity.webpageURL?.absoluteString ?? "nil")
- User Info: \(userActivity.userInfo ?? [:])
""")
return DeepLinkManager.shared.handleUniversalLink(userActivity)
}2. URL Scheme 無法運作
確認事項:
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
print("""
URL Scheme Debug:
- URL: \(url)
- Scheme: \(url.scheme ?? "nil")
- Host: \(url.host ?? "nil")
- Path: \(url.path)
- Query: \(url.query ?? "nil")
- Source App: \(options[.sourceApplication] ?? "nil")
""")
return DeepLinkManager.shared.handleDeepLink(url)
}3. 延遲深層連結無法運作
原因:
- 裝置指紋收集失敗
- 伺服器比對失敗
- 網路連線問題
確認:
// 延遲深層連結會在 initialize() 之後自動處理(無手動觸發 API)。
// 比對結果會透過 setDeepLinkListener 回呼傳遞,請在監聽器中確認記錄。
AdStageManager.shared.setDeepLinkListener { data in
print("✅ 디퍼드 딥링크 수신: \(data)")
}4. 應用程式位於背景時收不到深層連結
SceneDelegate 設定(iOS 13+):
import UIKit
import AdapterAdStage
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let _ = (scene as? UIWindowScene) else { return }
// 處理 URL Context
if let urlContext = connectionOptions.urlContexts.first {
_ = DeepLinkManager.shared.handleDeepLink(urlContext.url)
}
// 處理 User Activity
if let userActivity = connectionOptions.userActivities.first {
_ = DeepLinkManager.shared.handleUniversalLink(userActivity)
}
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
_ = DeepLinkManager.shared.handleDeepLink(url)
}
}
func scene(
_ scene: UIScene,
continue userActivity: NSUserActivity
) {
_ = DeepLinkManager.shared.handleUniversalLink(userActivity)
}
}5. 確認建置設定
Build Settings:
- Deployment Target: iOS 15.0 以上
- Swift Language Version: 5.0 以上
- Enable Bitcode: No (Xcode 14+ 已移除)Info.plist 必要項目:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>myapp</string>
</array>測試方式
1. URL Scheme 測試
於 Safari 中測試:
myapp://promo/summer
myapp://promo?campaign=summer&discount=20於終端機中測試:
xcrun simctl openurl booted "myapp://promo/summer"2. Universal Link 測試
於 Safari 中測試:
https://go.myapp.com/ABCDEF於「備忘錄」應用程式中測試:
- 於「備忘錄」應用程式中輸入連結
- 長按該連結
- 選擇「Open in [應用程式名稱]」
於終端機中測試:
xcrun simctl openurl booted "https://go.myapp.com/ABCDEF"3. 確認記錄
// 確認深層連結接收狀況
DeepLinkManager.shared.setDeepLinkListener { deepLinkData in
print("""
==========================================
딥링크 수신 테스트
==========================================
Short Path: \(deepLinkData.shortPath)
Link ID: \(deepLinkData.linkId)
Source: \(deepLinkData.source.description)
Parameters:
\(deepLinkData.parameters.map { " - \($0.key): \($0.value)" }.joined(separator: "\n"))
==========================================
""")
}
