モバイル 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)")
// 一般的な Web リンクの処理
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"
)
// Web の設定
.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メモ(Notes)アプリでテスト:
- メモアプリにリンクを入力
- リンクを長押し
- 「"[アプリ名]" で開く」を選択
ターミナルでテスト:
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"))
==========================================
""")
}
