StageUp
모바일 SDK딥링크

iOS

AdStage DeepLink 통합 가이드 (iOS)

목차

  1. 개요
  2. 프로젝트 설정
  3. Info.plist 설정
  4. AppDelegate 설정
  5. 딥링크 수신 처리
  6. 딥링크 생성
  7. 고급 기능
  8. 트러블슈팅

개요

AdStage DeepLink SDK는 다음 기능을 제공합니다:

  • 실시간 딥링크: URL Scheme, Universal Links를 통한 즉시 처리
  • 디퍼드 딥링크: 앱 설치 후 첫 실행 시 자동 복원
  • 동적 딥링크 생성: 서버 API를 통한 추적 가능한 링크 생성
  • 어트리뷰션 추적: UTM 파라미터 기반 마케팅 분석

프로젝트 설정

CocoaPods 설정

Podfile

platform :ios, '12.0'
 
target 'YourApp' do
  use_frameworks!
  
  # AdStage SDK
  pod 'AdapterAdStage', '3.0.5'
  
  # 의존성 (자동 설치됨)
  # Alamofire, SwiftyJSON 등
end
 
post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
    end
  end
end

설치:

pod install

Info.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>

Info.plist에 추가:

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

Xcode 설정:

  1. Signing & Capabilities 탭 선택
  2. + Capability 클릭
  3. Associated Domains 추가
  4. 도메인 추가:
    • applinks:go.myapp.com
    • applinks: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.setUnifiedListener { [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(from: 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: 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(from: 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.setUnifiedListener { deepLinkData in
            print("📱 딥링크 수신: \(deepLinkData.shortPath)")
            // 처리 로직
        }
    }
    
    func application(
        _ application: UIApplication,
        continue userActivity: NSUserActivity,
        restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
    ) -> Bool {
        // Universal Link 처리
        return DeepLinkManager.shared.handleUniversalLink(userActivity: userActivity)
    }
}

딥링크 수신 처리

1. 실시간 딥링크 처리

import AdapterAdStage
 
class DeepLinkHandler {
    
    static let shared = DeepLinkHandler()
    
    func setupListener() {
        DeepLinkManager.shared.setUnifiedListener { [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 호출
 
// 필요시 수동 처리:
Task {
    await DeferredDeepLinkHandler.shared.handleDeferredDeepLink()
}

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() {
        DeepLinkManager.shared.createDeepLink()
            .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)
                - ID: \(info.id)
                """)
                
                // 생성된 URL 공유
                self?.shareDeepLink(info.shortUrl)
            }
    }
    
    // 전체 옵션 포함
    func createFullDeepLink() {
        DeepLinkManager.shared.createDeepLink()
            // 기본 정보
            .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"
        ]
        
        DeepLinkManager.shared.createDeepLink()
            .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 = DeepLinkManager.shared.createDeepLink()
                .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 호환 방식

// Objective-C에서 사용 가능한 간단한 API
class ObjCCompatibleDeepLinkCreator {
    
    @objc func createSimpleDeepLink(name: String, completion: @escaping (String?, Error?) -> Void) {
        AdStageSDK.shared.createSimpleDeepLink(name: name, completion: completion)
    }
    
    @objc func createDeepLinkWithDescription(
        name: String,
        description: String,
        completion: @escaping (String?, Error?) -> Void
    ) {
        AdStageSDK.shared.createSimpleDeepLink(
            name: name,
            description: description,
            completion: completion
        )
    }
}

고급 기능

1. 전역 사용자 속성 설정

// 로그인 시
func onUserLogin(userId: String, userProfile: UserProfile) {
    AdStageManager.shared.setUserId(userId)
    
    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.setUserId(nil)
    AdStageManager.shared.setUserAttributes(nil)
    
    print("🚪 사용자 정보 초기화")
}

2. 전역 디바이스 정보 설정

import UIKit
 
func setupDeviceInfo() {
    let device = UIDevice.current
    let app = Bundle.main
    
    let deviceInfo = DeviceInfo(
        category: device.userInterfaceIdiom == .pad ? "tablet" : "mobile",
        platform: "iOS",
        model: device.model,
        appVersion: app.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown",
        osVersion: device.systemVersion
    )
    
    AdStageManager.shared.setDeviceInfo(deviceInfo)
    
    print("""
    📱 디바이스 정보 설정:
    - Category: \(deviceInfo.category)
    - Platform: \(deviceInfo.platform)
    - Model: \(deviceInfo.model)
    - App Version: \(deviceInfo.appVersion)
    - OS Version: \(deviceInfo.osVersion)
    """)
}

3. 프로모션 배너 연동

import AdapterAdStage
 
class PromotionManager {
    
    // 프로모션 리스트 조회
    func fetchPromotions() async {
        let params = AdStageSDK.PromotionParams()
        params.bannerType = "NATIVE"
        params.region = "KR"
        params.limit = NSNumber(value: 10)
        
        if let response = await AdStageSDK.shared.getPromotionList(params: params) {
            print("✅ 프로모션 \(response.promotions.count)개 조회")
            
            response.promotions.forEach { promotion in
                print("""
                - \(promotion.appName)
                  배너: \(promotion.bannerUrl)
                  CPI: \(promotion.cpi)
                """)
            }
        }
    }
    
    // 프로모션 배너 열기
    func openPromotionBanner() {
        let params = AdStageSDK.PromotionParams()
        params.bannerType = "INTERSTITIAL"
        params.region = "KR"
        
        AdStageSDK.shared.openPromotion(
            params: params,
            showTodayButton: true
        ) { url, error in
            if let error = error {
                print("❌ 프로모션 열기 실패: \(error.localizedDescription)")
                return
            }
            
            if let url = url {
                print("✅ 프로모션 URL: \(url)")
            }
        }
    }
}

트러블슈팅

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: 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(from: url)
}

3. 디퍼드 딥링크가 작동하지 않음

원인:

  • 디바이스 핑거프린트 수집 실패
  • 서버 매칭 실패
  • 네트워크 연결 문제

확인:

// 수동으로 디퍼드 딥링크 처리
Task {
    await DeferredDeepLinkHandler.shared.handleDeferredDeepLink()
}
 
// 로그 확인
print("✅ 디퍼드 딥링크 처리 시작")

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(from: urlContext.url)
        }
        
        // User Activity 처리
        if let userActivity = connectionOptions.userActivities.first {
            _ = DeepLinkManager.shared.handleUniversalLink(userActivity: userActivity)
        }
    }
    
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        if let url = URLContexts.first?.url {
            _ = DeepLinkManager.shared.handleDeepLink(from: url)
        }
    }
    
    func scene(
        _ scene: UIScene,
        continue userActivity: NSUserActivity
    ) {
        _ = DeepLinkManager.shared.handleUniversalLink(userActivity: userActivity)
    }
}

5. 빌드 설정 확인

Build Settings:

- Deployment Target: iOS 12.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"

Safari에서 테스트:

https://go.myapp.com/ABCDEF

Notes 앱에서 테스트:

  1. Notes 앱에서 링크 입력
  2. 링크를 길게 누르기
  3. "Open in [앱이름]" 선택

터미널에서 테스트:

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

3. 로그 확인

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

참고 자료


목차