Mobile SDKDeep Link
iOS
AdStage DeepLink Integration Guide (iOS)
Table of Contents
- Overview
- Project Setup
- Info.plist Configuration
- AppDelegate Setup
- Handling Deep Link Reception
- Creating Deep Links
- Advanced Features
- Troubleshooting
Overview
The AdStage DeepLink SDK provides the following features:
- Real-time Deep Links: Immediate processing via URL Scheme and Universal Links
- Deferred Deep Links: Automatic restoration on first launch after app installation
- Dynamic Deep Link Creation: Trackable link generation through server API
- Attribution Tracking: Marketing analysis based on UTM parameters
Project Setup
CocoaPods Configuration
Podfile
platform :ios, '12.0'
target 'YourApp' do
use_frameworks!
# AdStage SDK
pod 'AdapterAdStage', '~> 3.0'
# Dependencies (automatically installed)
# Alamofire, SwiftyJSON, etc.
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
endInstallation:
pod installSwift Package Manager (SPM)
// Package.swift
dependencies: [
.package(url: "https://github.com/nbase-io/NBase-SDK-iOS.git", from: "3.0.0")
]Info.plist Configuration
1. URL Scheme Setup
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>2. Universal Links Setup
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:
- Select Signing & Capabilities tab
- Click + Capability
- Add Associated Domains
- Add domains:
applinks:go.myapp.comapplinks:go.adstage.net
3. Apple App Site Association File
Deploy to server: https://go.myapp.com/.well-known/apple-app-site-association
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAM_ID.com.example.myapp",
"paths": ["*"]
}
]
}
}Important Notes:
- No file extension (don't add
.json) - Content-Type:
application/json - HTTPS required
- Place in root path or
.well-knownfolder
4. Required Permissions
<!-- Network communication -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
<!-- Background modes (optional) -->
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>AppDelegate Setup
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. Initialize AdStage SDK
initializeAdStage()
// 2. Setup deep link listener
setupDeepLinkListener()
return true
}
// MARK: - AdStage Initialization
private func initializeAdStage() {
AdStageManager.shared.initialize(
apiKey: "your-api-key-here",
serverUrl: "https://api.adstage.app" // Optional
)
print("✅ AdStage SDK initialized")
}
private func setupDeepLinkListener() {
// Setup unified deep link listener
DeepLinkManager.shared.setUnifiedListener { [weak self] deepLinkData in
guard let self = self else { return }
print("""
📱 Deep link received:
- Short Path: \(deepLinkData.shortPath)
- Link ID: \(deepLinkData.linkId)
- Source: \(deepLinkData.source.description)
- Parameters: \(deepLinkData.parameters)
""")
// Handle deep link
self.handleDeepLink(deepLinkData)
}
}
// MARK: - Deep Link Handling
private func handleDeepLink(_ data: DeepLinkData) {
// Handle by source
switch data.source {
case .realtime:
print("🔗 Real-time deep link handling")
handleRealtimeDeepLink(data)
case .install:
print("📦 Deferred deep link handling (first launch after install)")
handleDeferredDeepLink(data)
case .unknown:
print("❓ Unknown deep link source")
}
}
private func handleRealtimeDeepLink(_ data: DeepLinkData) {
// Navigate based on parameters
if let campaign = data.parameters["campaign"] {
navigateToCampaign(campaign)
} else if let promo = data.parameters["promo"] {
navigateToPromotion(promo)
} else {
navigateToHome()
}
}
private func handleDeferredDeepLink(_ data: DeepLinkData) {
// Handle after onboarding or immediately
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.handleRealtimeDeepLink(data)
}
}
// MARK: - Navigation
private func navigateToCampaign(_ campaign: String) {
print("🎯 Navigate to campaign: \(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("🎁 Apply promotion: \(promo)")
// Promotion handling logic
}
private func navigateToHome() {
print("🏠 Navigate to home")
// Home navigation
}
// MARK: - URL Scheme Deep Link (iOS 8+)
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
print("📲 URL Scheme deep link received: \(url)")
// Handle by AdStage
let handled = DeepLinkManager.shared.handleDeepLink(from: url)
if handled {
print("✅ AdStage handled the deep link")
return true
}
// Handle other URL Schemes (e.g., OAuth callback)
if url.scheme == "myapp" {
// Custom handling
return handleCustomScheme(url)
}
return false
}
// MARK: - Universal Links (iOS 9+)
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
// Check if it's a Universal Link
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
print("🌐 Universal Link received: \(url)")
// Handle by AdStage
let handled = DeepLinkManager.shared.handleUniversalLink(userActivity: userActivity)
if handled {
print("✅ AdStage handled the Universal Link")
return true
}
// Handle other Universal Links
return handleCustomUniversalLink(url)
}
// MARK: - Custom Handlers
private func handleCustomScheme(_ url: URL) -> Bool {
print("🔧 Custom URL Scheme handling: \(url)")
// OAuth, payment callbacks, etc.
return false
}
private func handleCustomUniversalLink(_ url: URL) -> Bool {
print("🔧 Custom Universal Link handling: \(url)")
// General web link handling
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
// Handle URL Scheme
print("📲 URL received: \(url)")
_ = DeepLinkManager.shared.handleDeepLink(from: url)
}
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
// Initialize AdStage
AdStageManager.shared.initialize(
apiKey: "your-api-key-here",
serverUrl: "https://api.adstage.app"
)
// Setup deep link listener
setupDeepLinkListener()
return true
}
private func setupDeepLinkListener() {
DeepLinkManager.shared.setUnifiedListener { deepLinkData in
print("📱 Deep link received: \(deepLinkData.shortPath)")
// Handle logic
}
}
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
// Handle Universal Link
return DeepLinkManager.shared.handleUniversalLink(userActivity: userActivity)
}
}Handling Deep Link Reception
1. Real-time Deep Link Handling
import AdapterAdStage
class DeepLinkHandler {
static let shared = DeepLinkHandler()
func setupListener() {
DeepLinkManager.shared.setUnifiedListener { [weak self] deepLinkData in
guard let self = self else { return }
// Extract deep link data
let shortPath = deepLinkData.shortPath
let parameters = deepLinkData.parameters
let source = deepLinkData.source
print("""
✅ Deep link received successfully
- Short Path: \(shortPath)
- Link ID: \(deepLinkData.linkId)
- Source: \(source.description)
- Event Type: \(deepLinkData.eventType)
""")
// Extract UTM parameters
let utmParams = deepLinkData.getUtmParameters()
print("📊 UTM Parameters: \(utmParams)")
// Extract custom parameters
let customParams = deepLinkData.getCustomParameters()
print("🔧 Custom Parameters: \(customParams)")
// Handle business logic
self.processDeepLink(deepLinkData)
}
}
private func processDeepLink(_ data: DeepLinkData) {
// Branch by parameter
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 deep link: \(campaign)")
DispatchQueue.main.async {
// Navigate to campaign screen
NotificationCenter.default.post(
name: .navigateToCampaign,
object: nil,
userInfo: ["campaign": campaign, "data": data]
)
}
}
private func handleProductDeepLink(_ productId: String, data: DeepLinkData) {
print("🛍️ Product deep link: \(productId)")
DispatchQueue.main.async {
NotificationCenter.default.post(
name: .navigateToProduct,
object: nil,
userInfo: ["productId": productId]
)
}
}
private func handlePromoDeepLink(_ promo: String, data: DeepLinkData) {
print("🎁 Promotion deep link: \(promo)")
// Auto-apply promotion code
applyPromoCode(promo)
}
private func handleDefaultDeepLink(_ data: DeepLinkData) {
print("📋 Default deep link handling")
DispatchQueue.main.async {
// Navigate to home
NotificationCenter.default.post(name: .navigateToHome, object: nil)
}
}
private func applyPromoCode(_ code: String) {
// Promotion application logic
print("Applying promo code: \(code)")
}
}
// Notification name definitions
extension Notification.Name {
static let navigateToCampaign = Notification.Name("navigateToCampaign")
static let navigateToProduct = Notification.Name("navigateToProduct")
static let navigateToHome = Notification.Name("navigateToHome")
}2. Deferred Deep Links (Automatic Handling)
// Deferred deep links are automatically handled at AdStageManager.initialize()
// No separate implementation required!
// Automatically at initialization:
// 1. Collect device fingerprint
// 2. Request matching from server
// 3. Automatically call onDeepLinkReceived if saved deep link exists
// Manual handling if needed:
Task {
await DeferredDeepLinkHandler.shared.handleDeferredDeepLink()
}3. Handling by Source
func handleDeepLink(_ data: DeepLinkData) {
switch data.source {
case .realtime:
print("🔗 Real-time deep link - received while app running")
// Can navigate immediately
navigateImmediately(data)
case .install:
print("📦 Deferred deep link - first launch after install")
// Handle after onboarding or delay
showOnboardingThenNavigate(data)
case .unknown:
print("❓ Unknown source")
handleAsDefault(data)
}
}
private func showOnboardingThenNavigate(_ data: DeepLinkData) {
// Handle after onboarding completion
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
self?.navigateImmediately(data)
}
}Creating Deep Links
1. Builder Pattern
import AdapterAdStage
class DeepLinkCreator {
// Simple deep link creation
func createSimpleDeepLink() {
DeepLinkManager.shared.createDeepLink()
.setName("Summer Promotion")
.setDescription("2024 Summer Season Promotion")
.setChannel("google-ads")
.setCampaign("summer2024")
.addParameter(key: "promo", value: "SUMMER20")
.create { [weak self] deepLinkInfo, error in
if let error = error {
print("❌ Deep link creation failed: \(error.localizedDescription)")
return
}
guard let info = deepLinkInfo else {
print("❌ No deep link info")
return
}
print("""
✅ Deep link created
- Short URL: \(info.shortUrl)
- Short Path: \(info.shortPath)
- ID: \(info.id)
""")
// Share generated URL
self?.shareDeepLink(info.shortUrl)
}
}
// Full options included
func createFullDeepLink() {
DeepLinkManager.shared.createDeepLink()
// Basic info
.setName("Winter Promotion")
.setDescription("2024-2025 Winter Season Promotion")
.setShortPath("WINTER24") // Custom path
// Tracking parameters
.setChannel("facebook-ads")
.setSubChannel("instagram")
.setCampaign("winter2024")
.setAdGroup("fashion-lovers")
.setCreative("banner-001")
.setContent("hero-image")
.setKeyword("winter-sale")
// Android configuration
.setAndroidConfig(
packageName: "com.example.myapp",
appScheme: "myapp://promo/winter",
webUrl: "https://example.com/promo/winter"
)
// iOS configuration
.setIosConfig(
appStoreId: "123456789",
appScheme: "myapp://promo/winter",
webUrl: "https://example.com/promo/winter"
)
// Web configuration
.setWebConfig(webUrl: "https://example.com/promo/winter")
// Custom parameters
.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: \(error.localizedDescription)")
return
}
guard let info = deepLinkInfo else { return }
print("✅ Short URL: \(info.shortUrl)")
}
}
// Set multiple parameters at once
func createDeepLinkWithMultipleParams() {
let parameters: [String: String] = [
"campaign": "spring2024",
"discount": "15",
"promoCode": "SPRING15",
"source": "email"
]
DeepLinkManager.shared.createDeepLink()
.setName("Spring Promotion")
.setChannel("email-marketing")
.setParameters(parameters)
.create { deepLinkInfo, error in
// Handle
}
}
// Share functionality
private func shareDeepLink(_ url: String) {
DispatchQueue.main.async {
let text = """
🎁 Special Promotion Invite!
Sign up through this link and get a $50 discount coupon.
\(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 = DeepLinkManager.shared.createDeepLink()
.setName("New User Event")
.setChannel("referral")
.setCampaign("invite-friend")
.addParameter(key: "referrer", value: "USER123")
.addParameter(key: "bonus", value: "5000")
let info = try await builder.createAsync()
print("✅ Deep link created: \(info.shortUrl)")
// Update UI
await MainActor.run {
self.updateUI(with: info.shortUrl)
}
} catch {
print("❌ Deep link creation failed: \(error.localizedDescription)")
await MainActor.run {
self.showError(error)
}
}
}
@MainActor
private func updateUI(with url: String) {
// Update UI
}
@MainActor
private func showError(_ error: Error) {
// Show error
}
}3. Objective-C Compatible Approach
// Simple API available from Objective-C
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
)
}
}Advanced Features
1. Global User Attributes Setup
// On login
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("✅ User info setup complete")
}
// On logout
func onUserLogout() {
AdStageManager.shared.setUserId(nil)
AdStageManager.shared.setUserAttributes(nil)
print("🚪 User info cleared")
}2. Global Device Info Setup
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("""
📱 Device info setup:
- Category: \(deviceInfo.category)
- Platform: \(deviceInfo.platform)
- Model: \(deviceInfo.model)
- App Version: \(deviceInfo.appVersion)
- OS Version: \(deviceInfo.osVersion)
""")
}3. Promotion Banner Integration
import AdapterAdStage
class PromotionManager {
// Fetch promotion list
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("✅ Fetched \(response.promotions.count) promotions")
response.promotions.forEach { promotion in
print("""
- \(promotion.appName)
Banner: \(promotion.bannerUrl)
CPI: \(promotion.cpi)
""")
}
}
}
// Open promotion banner
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("❌ Failed to open promotion: \(error.localizedDescription)")
return
}
if let url = url {
print("✅ Promotion URL: \(url)")
}
}
}
}Troubleshooting
1. Universal Links Not Working
Checklist:
- Verify Associated Domains setup
- Verify apple-app-site-association file deployment
- Verify HTTPS usage
- Verify Team ID and Bundle ID match
Verification Method:
# 1. Check file accessibility
curl https://go.myapp.com/.well-known/apple-app-site-association
# 2. Check Apple CDN cache
curl https://app-site-association.cdn-apple.com/a/v1/go.myapp.comDebugging:
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 Not Working
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(from: url)
}3. Deferred Deep Links Not Working
Causes:
- Device fingerprint collection failed
- Server matching failed
- Network connection issue
Check:
// Manually handle deferred deep link
Task {
await DeferredDeepLinkHandler.shared.handleDeferredDeepLink()
}
// Check logs
print("✅ Starting deferred deep link handling")4. Deep Links Not Received When App in Background
SceneDelegate Setup (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 }
// Handle URL Context
if let urlContext = connectionOptions.urlContexts.first {
_ = DeepLinkManager.shared.handleDeepLink(from: urlContext.url)
}
// Handle 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 Check
Build Settings:
- Deployment Target: iOS 12.0 or higher
- Swift Language Version: 5.0 or higher
- Enable Bitcode: No (removed in Xcode 14+)Required Info.plist Items:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>myapp</string>
</array>Testing Methods
1. URL Scheme Testing
Testing in Safari:
myapp://promo/summer
myapp://promo?campaign=summer&discount=20Testing in Terminal:
xcrun simctl openurl booted "myapp://promo/summer"2. Universal Link Testing
Testing in Safari:
https://go.myapp.com/ABCDEFTesting in Notes App:
- Enter link in Notes app
- Long press the link
- Select "Open in [app name]"
Testing in Terminal:
xcrun simctl openurl booted "https://go.myapp.com/ABCDEF"3. Log Verification
// Verify deep link reception
DeepLinkManager.shared.setUnifiedListener { deepLinkData in
print("""
==========================================
Deep Link Reception Test
==========================================
Short Path: \(deepLinkData.shortPath)
Link ID: \(deepLinkData.linkId)
Source: \(deepLinkData.source.description)
Parameters:
\(deepLinkData.parameters.map { " - \($0.key): \($0.value)" }.joined(separator: "\n"))
==========================================
""")
}
