StageUp
Mobile SDKDeep Link

iOS

AdStage DeepLink Integration Guide (iOS)

Table of Contents

  1. Overview
  2. Project Setup
  3. Info.plist Configuration
  4. AppDelegate Configuration
  5. Handling Deep Link Reception
  6. Creating Deep Links
  7. Advanced Features
  8. Troubleshooting

Overview

The AdStage DeepLink SDK provides the following features:

  • Real-time deep links: Instant handling via URL Scheme and Universal Links
  • Deferred deep links: Automatic restoration on the first launch after app installation
  • Dynamic deep link creation: Trackable link generation via the server API
  • Attribution tracking: Marketing analytics based on UTM parameters

Project Setup

CocoaPods Configuration

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

Install:

pod install

Info.plist Configuration

1. URL Scheme Configuration

Add to 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>

Add to Info.plist:

<key>com.apple.developer.associated-domains</key>
<array>
    <string>applinks:go.myapp.com</string>
    <string>applinks:go.adstage.net</string>
</array>

Xcode configuration:

  1. Select the Signing & Capabilities tab
  2. Click + Capability
  3. Add Associated Domains
  4. Add domains:
    • applinks:go.myapp.com
    • applinks:go.adstage.net

3. Apple App Site Association File

Deploy to the server: https://go.myapp.com/.well-known/apple-app-site-association

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAM_ID.com.example.myapp",
        "paths": ["*"]
      }
    ]
  }
}

Notes:

  • No file extension (do not append .json)
  • Content-Type: application/json
  • HTTPS required
  • Place at the root path or in the .well-known folder

4. Required Permission Settings

<!-- 네트워크 통신 -->
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
</dict>
 
<!-- 백그라운드 모드 (선택사항) -->
<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>remote-notification</string>
</array>

AppDelegate Configuration

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 Structure

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)
    }
}

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")
}
// 디퍼드 딥링크는 AdStageManager.initialize() 시점에 자동으로 처리됩니다.
// 별도 구현 불필요!
 
// 초기화 시 자동으로:
// 1. 디바이스 핑거프린트 수집
// 2. 서버에 매칭 요청
// 3. 저장된 딥링크가 있으면 자동으로 onDeepLinkReceived 호출
 
// 디퍼드 딥링크는 수동 트리거 API를 노출하지 않으며, initialize() 이후 자동 처리됩니다.
// 매칭 결과는 위에서 등록한 setDeepLinkListener 콜백으로 전달됩니다.

3. Handling by Source

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 Pattern

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 Approach

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 Compatible Approach

createDeepLinkBuilder(), the chaining methods of DeepLinkBuilder, and create(completion:) are all exposed as @objc, so they can be used as-is from Objective-C. You can build a simple wrapper that receives only the Short URL string via a callback.

// 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)
            }
    }
}

Advanced Features

1. Setting Global User Attributes

Setting user information with UserAttributes automatically includes it in events sent afterward. setUserAttributes(_:) takes a UserAttributes object as its argument, and all fields are optional.

// 로그인 시
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("🚪 사용자 정보 초기화")
}

Note: Device information (platform, model, app version, OS version, IDFV/IDFA, ATT status, etc.) is automatically collected and included by the SDK at the time of event transmission, so there is no need to set it separately.


Troubleshooting

Checklist:

  • Verify Associated Domains configuration
  • Verify apple-app-site-association file deployment
  • Verify HTTPS usage
  • Verify Team ID and Bundle ID match

Verification method:

# 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

Debugging:

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 Not Working

Things to check:

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)
}

Causes:

  • Device fingerprint collection failure
  • Server matching failure
  • Network connection problem

Check:

// 디퍼드 딥링크는 initialize() 이후 자동 처리됩니다(수동 트리거 API 없음).
// 매칭 결과는 setDeepLinkListener 콜백으로 전달되므로 리스너에서 로그를 확인하세요.
AdStageManager.shared.setDeepLinkListener { data in
    print("✅ 디퍼드 딥링크 수신: \(data)")
}

SceneDelegate configuration (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. Verifying Build Settings

Build Settings:

- Deployment Target: iOS 15.0 이상
- Swift Language Version: 5.0 이상
- Enable Bitcode: No (Xcode 14+에서는 제거됨)

Required Info.plist entries:

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>myapp</string>
</array>

Testing Methods

1. URL Scheme Testing

Test in Safari:

myapp://promo/summer
myapp://promo?campaign=summer&discount=20

Test in the terminal:

xcrun simctl openurl booted "myapp://promo/summer"

Test in Safari:

https://go.myapp.com/ABCDEF

Test in the Notes app:

  1. Enter the link in the Notes app
  2. Long-press the link
  3. Select "Open in [app name]"

Test in the terminal:

xcrun simctl openurl booted "https://go.myapp.com/ABCDEF"

3. Checking Logs

// 딥링크 수신 확인
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"))
    ==========================================
    """)
}

References


Table of Contents