iOS SDK - 基本統合


前提条件

この統合を進める前に、 Singular SDK の統合:計画と前提条件 の手順を完了してください。

重要: これらの前提条件の手順は、Singular SDK の統合に必須です。

iOS 固有のセットアップ

SDK を初期化する前に、Xcode プロジェクトで以下が設定されていることを確認してください。これらのいずれかが欠けていることが、SDK の無音障害の一般的な原因です。

Info.plist のエントリ:

  • NSUserTrackingUsageDescription — アプリがトラッキングを要求する理由を説明する、ユーザー向けの文字列です。App Tracking Transparency (ATT) プロンプトを表示するか、IDFA を読み取る場合は Apple により必須となります。Singular SDK は ATT を自動的に尊重します。このキーがなくても SDK は動作しますが、iOS 14.5 以降では IDFA にアクセスできません。
  • URL Types と CFBundleURLSchemes — ディープリンク戦略でカスタム URI スキーム(例: myapp:// )を使用する場合に必要です。HTTPS 経由の Universal Links / Singular Links には不要です。

エンタイトルメント(Signing & Capabilities):

  • Associated Domains — Singular のブランドドメインごとに、 applinks:yourcompany.sng.link の形式でエントリを追加します。Universal Link / Singular Link のアトリビューションに必須です。この Singular ホストドメインは、Apple がインストール時にダウンロードする apple-app-site-association (AASA) JSON ファイルをホストします。AASA ファイルは、Singular プラットフォームでアプリのリンクが作成された際に生成および更新されます。
  • Push Notifications — アンインストール トラッキングおよびプッシュ アトリビューションによる再エンゲージメントに必須です。Push Notifications のサポートおよびアンインストール トラッキングに関する記事を参照してください。

必須のシステムフレームワーク(手動統合の場合のみ — CocoaPods および SwiftPM は自動的にリンクします):

  • StoreKit.framework — IAP および SKAdNetwork。
  • AdServices.framework — Apple Search Ads アトリビューション トークン(iOS 14.3 以降)。
  • AppTrackingTransparency.framework — ATT プロンプトと認可ステータス(iOS 14.5 以降)。
  • UserNotifications.framework — プッシュ通知。
  • WebKit.framework — Singular の WKWebView JS ブリッジを使用する場合のみ必要です。

インストール

お好みのインストール方法を選択してください。ほとんどのプロジェクトでは CocoaPods を推奨します。

クイック判断:

  • すでに CocoaPods を使用していますか? 方法 1 を使用
  • SPM のみのプロジェクトですか? 方法 2 を使用
  • パッケージマネージャーを使用していませんか? 方法 3 を使用

インストール方法

方法 1:CocoaPods(推奨)

方法 1:CocoaPods(推奨)

要件:

  • CocoaPods がインストールされていること( インストールガイド
  • プロジェクトディレクトリへのターミナルアクセス

インストール手順:

  1. Podfile の初期化 (すでにある場合はスキップ):

    Terminal
    cd /path/to/your/project
    pod init
  2. Singular SDK を追加 して Podfile に記述:

    Podfile
    platform :ios, '12.0'
    
    target 'YourAppName' do
      use_frameworks!
    
      # Singular SDK
      pod 'Singular-SDK'
    
    end
  3. 依存関係のインストール:

    Terminal
    pod install
  4. ワークスペースを開く: 今後は、 .xcodeproj ではなく .xcworkspace を開いてください
  5. Swift プロジェクトのみ: ブリッジングヘッダーを作成 (以下を参照)
方法 2:Swift Package Manager

方法 2:Swift Package Manager

インストール手順:

  1. Xcode で: File → Add Packages
  2. リポジトリ URL を入力: https://github.com/singular-labs/Singular-iOS-SDK
  3. バージョンを選択して Add Package をクリック
  4. 必須フレームワークを追加:
    Build Phases → Link Binary with Libraries に移動して、以下を追加:
    • 必須のライブラリをリンク:
      Build Phases → Link Binary With Libraries に移動して、以下を追加:
      • Libsqlite3.0.tbd
      • SystemConfiguration.framework
      • Security.framework
      • Libz.tbd
      • AdSupport.framework
      • WebKit.framework
      • StoreKit.framework
      • AdServices.framework(Optional としてマーク)
  5. Swift プロジェクトのみ: ブリッジングヘッダーを作成 (以下を参照)
方法 3:フレームワークの手動インストール

方法 3:フレームワークの手動インストール

使用する場合: CocoaPods または SPM が使用できない場合にのみ、この方法を使用してください。

フレームワークをダウンロード:

インストール手順:

  1. ダウンロードしたフレームワークを解凍
  2. Xcode で:プロジェクトを右クリック → Add Files To [Project]
  3. Create Groups を選択し、フレームワークフォルダーを追加
  4. 必須のライブラリをリンク:
    Build Phases → Link Binary With Libraries に移動して、以下を追加:
    • Libsqlite3.0.tbd
    • SystemConfiguration.framework
    • Security.framework
    • Libz.tbd
    • AdSupport.framework
    • WebKit.framework
    • StoreKit.framework
    • AdServices.framework(Optional としてマーク)
  5. フレームワークの埋め込み:
    General → Frameworks, Libraries, and Embedded Content に移動
    Singular フレームワークを Embed & Sign に設定

Swift ブリッジングヘッダー

重要: CocoaPods または SPM を使用する Swift プロジェクトには必須です。

  1. ヘッダーファイルを作成:
    Xcode → File → New → File → Header File
    名前を YourProjectName-Bridging-Header.h とする
  2. インポートを追加:

    Bridging Header
    #import <Singular/Singular.h>
  3. ビルド設定でリンク:
    Build Settings → Objective-C Bridging Header
    次のように設定: YourProjectName/YourProjectName-Bridging-Header.h

SDK の設定と初期化

設定オブジェクトを作成し、アプリのエントリポイントで SDK を初期化します。


設定オブジェクトの作成

基本設定

SDK 認証情報とオプション機能を含む SingularConfig オブジェクトを作成します。この設定は、すべてのアプリアーキテクチャで共通です。

認証情報を取得: Singular プラットフォームの Developer Tools → SDK Integration で、SDK Key と SDK Secret を確認してください。

Swift Objective-C
// MARK: - Singular Configuration
private func getConfig() -> SingularConfig? {
    // Create config with your credentials
    guard let config = SingularConfig(
        apiKey: "YOUR_SDK_KEY",
        andSecret: "YOUR_SDK_SECRET"
    ) else {
        return nil
    }

    // OPTIONAL: Wait for ATT consent (if showing ATT prompt)
    // Remove this line if NOT using App Tracking Transparency
    config.waitForTrackingAuthorizationWithTimeoutInterval = 300

    // OPTIONAL: Support custom ESP domains for deep links
    config.espDomains = ["links.your-domain.com"]

    // OPTIONAL: Handle deep links
    config.singularLinksHandler = { params in
        if let params = params {
          self.handleDeeplink(params)
        }
    }

    return config
}

// MARK: - OPTIONAL: Deep link handler implementation
private func handleDeeplink(_ params: SingularLinkParams) {
    // Guard clause: Exit if no deep link provided
    guard let deeplink = params.getDeepLink() else {
      return
    }

    // Extract deep link parameters
    let passthrough = params.getPassthrough()
    let isDeferred = params.isDeferred()
    let urlParams = params.getUrlParameters()

    #if DEBUG
    // Debug logging only - stripped from production builds
    print("Singular Links Handler")
    print("Singular deeplink received:", deeplink)
    print("Singular passthrough received:", passthrough ?? "none")
    print("Singular isDeferred received:", isDeferred ? "YES" : "NO")
    print("Singular URL Params received:", urlParams ?? [:])
    #endif

    // TODO: Navigate to appropriate screen based on deep link
    // Add deep link handling code here. Navigate to appropriate screen.
}

SKAdNetwork は自動有効化: SDK バージョン 12.0.6 以降、SKAdNetwork はデフォルトで有効化されています。追加の設定は不要です。


SDK の初期化

アプリのアーキテクチャを選択

すべてのアプリエントリポイントで SDK を初期化します。初期化パターンは、アプリのアーキテクチャによって異なります。

どのアーキテクチャを使用していますか?

  • SceneDelegate: プロジェクトに SceneDelegate.swift または SceneDelegate.m があるか確認
  • SwiftUI: アプリが @main struct YourApp: App で始まる
  • AppDelegate のみ: iOS 13 より前のアプリ、または SceneDelegate のないアプリ

最新の iOS:SceneDelegate(iOS 13 以降)

最新の iOS:SceneDelegate(iOS 13 以降)

コードの追加場所: SceneDelegate.swift または SceneDelegate.m

初期化するエントリポイント:

  • willConnectTo session - アプリの起動
  • continue userActivity - Universal Links
  • openURLContexts - ディープリンクスキーム
Swift Objective-C
import Singular

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    // 1️⃣ App launch
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
              options connectionOptions: UIScene.ConnectionOptions) {

        // ANTI-SWIZZLING: Capture deep link parameters IMMEDIATELY before any
        // other code runs. This prevents third-party SDKs from intercepting
        // or modifying these values.
        let userActivity = connectionOptions.userActivities.first
        let urlContext = connectionOptions.urlContexts.first
        let openUrl = urlContext?.url

        #if DEBUG
        // Log captured values to detect swizzling interference
        print("[SWIZZLE CHECK] UserActivity captured:", userActivity?.webpageURL?.absoluteString ?? "none")
        print("[SWIZZLE CHECK] URL Context captured:", openUrl?.absoluteString ?? "none")
        print("IDFV:", UIDevice.current.identifierForVendor?.uuidString ?? "N/A")
        #endif

        // Create window from windowScene
        guard let windowScene = scene as? UIWindowScene else { return }
        window = UIWindow(windowScene: windowScene)
        window?.rootViewController = UIViewController() // Replace with your root VC
        window?.makeKeyAndVisible()

        // Singular initialization - uses captured values to avoid swizzling conflicts
        guard let config = getConfig() else { return }

        // Pass Universal Link if available
        if let userActivity = userActivity {
            config.userActivity = userActivity
        }

        // Pass URL scheme if available
        // CRITICAL for custom URL scheme attribution
        if let openUrl = openUrl {
            config.openUrl = openUrl
        }

        // Initialize Singular SDK
        Singular.start(config)
    }

    // 2️⃣ Universal Links
    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        guard let config = getConfig() else { return }
        config.userActivity = userActivity

        // Initialize Singular SDK
        Singular.start(config)
    }

    // 3️⃣ Deep link schemes
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let config = getConfig() else { return }

        if let url = URLContexts.first?.url {
            config.openUrl = url
        }

        // Initialize Singular SDK
        Singular.start(config)
    }
}
最新の iOS:SwiftUI アプリ(iOS 14 以降)

最新の iOS:SwiftUI アプリ(iOS 14 以降)

コードの追加場所: メインの App 構造体ファイル

初期化するエントリポイント:

  • .onOpenURL(of: scenePhase) - カスタム URL スキームを処理
  • .onContinueUserActivity(of: scenePhase) - Universal Links を処理(Singular Deep Links)
  • .onChange.active - ディープリンクが発生しなかった場合の初回起動時の初期化を処理。Deferred Deeplinks を処理します。
Swift
import SwiftUI
import Singular

@main
struct simpleSwiftUIApp: App {
    @Environment(\.scenePhase) var scenePhase
    @State private var hasInitialized = false

    var body: some Scene {
        WindowGroup {
            ContentView()
                // 1️⃣ Handle custom URL schemes (e.g., myapp://path)
                .onOpenURL { url in
                    #if DEBUG
                    print("[Singular] URL Scheme:", url.absoluteString)
                    #endif

                    guard let config = getConfig() else { return }
                    config.openUrl = url
                    Singular.start(config)
                    hasInitialized = true
                }

                // 2️⃣ Handle Universal Links (e.g., https://links.your-domain.com)
                .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
                    #if DEBUG
                    print("[Singular] Universal Link:", userActivity.webpageURL?.absoluteString ?? "none")
                    #endif

                    guard let config = getConfig() else { return }
                    config.userActivity = userActivity
                    Singular.start(config)
                    hasInitialized = true
                }
        }
        .onChange(of: scenePhase) { oldPhase, newPhase in
            switch newPhase {
            case .active:
                // 3️⃣ Initialize ONLY on first launch if no deep link occurred
                guard !hasInitialized else {
                    #if DEBUG
                    print("[Singular] Already initialized, skipping")
                    #endif
                    return
                }

                #if DEBUG
                if let idfv = UIDevice.current.identifierForVendor?.uuidString {
                    print("[Singular] IDFV:", idfv)
                }
                #endif

                guard let config = getConfig() else { return }
                Singular.start(config)
                hasInitialized = true

            case .background:
                #if DEBUG
                print("[Singular] App backgrounded")
                #endif

            case .inactive:
                #if DEBUG
                print("[Singular] App inactive")
                #endif

            @unknown default:
                break
            }
        }
    }

    // Add your getConfig() function here
    // MARK: - Singular Configuration
    private func getConfig() -> SingularConfig? {
        // ... (same as above)
    }

    // Add your handleDeeplink() function here
    // MARK: - Deep Link Handler
    private func handleDeeplink(_ params: SingularLinkParams) {
        // ... (same as above)
    }
}
レガシー iOS:AppDelegate(iOS 13 より前)

レガシー iOS:AppDelegate(iOS 13 より前)

コードの追加場所: AppDelegate.swift または AppDelegate.m

初期化するエントリポイント:

  • didFinishLaunchingWithOptions - アプリの起動
  • continue userActivity - Universal Links
  • open url - ディープリンクスキーム
Swift Objective-C
import Singular
import UIKit


class AppDelegate: UIResponder, UIApplicationDelegate {

    // 1️⃣ App launch
    func application(_ application: UIApplication,
                    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // ANTI-SWIZZLING: Capture deep link parameters IMMEDIATELY before any
        // other code runs. This prevents third-party SDKs from intercepting
        // or modifying these values.
        let launchUrl = launchOptions?[.url] as? URL
        let userActivityDictionary = launchOptions?[.userActivityDictionary] as? [String: Any]
        let userActivity = userActivityDictionary?["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity

        #if DEBUG
        // Log captured values to detect swizzling interference
        print("[SWIZZLE CHECK] Launch URL captured:", launchUrl?.absoluteString ?? "none")
        print("[SWIZZLE CHECK] UserActivity captured:", userActivity?.webpageURL?.absoluteString ?? "none")
        print("IDFV:", UIDevice.current.identifierForVendor?.uuidString ?? "N/A")
        #endif

        // Singular initialization - uses captured values to avoid swizzling conflicts
        guard let config = getConfig() else { return true }

        // Pass the entire launchOptions dictionary for Singular's internal processing
        config.launchOptions = launchOptions

        // Explicitly pass Universal Link if available
        // CRITICAL for universal link attribution
        if let userActivity = userActivity {
            config.userActivity = userActivity
        }

        // Explicitly pass URL scheme if available
        // CRITICAL for custom URL scheme attribution
        if let launchUrl = launchUrl {
            config.openUrl = launchUrl
        }

        // Initialize Singular SDK
        Singular.start(config)

        return true
    }

    // 2️⃣ Universal Links
    func application(_ application: UIApplication,
                    continue userActivity: NSUserActivity,
                    restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void) -> Bool {

        #if DEBUG
        print("[SWIZZLE CHECK] Universal Link handler called:", userActivity.webpageURL?.absoluteString ?? "none")
        #endif

        guard let config = getConfig() else { return true }
        config.userActivity = userActivity
        Singular.start(config)

        return true
    }

    // 3️⃣ Deep link schemes
    func application(_ app: UIApplication,
                    open url: URL,
                    options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

        #if DEBUG
        print("[SWIZZLE CHECK] URL Scheme handler called:", url.absoluteString)
        #endif

        guard let config = getConfig() else { return true }
        config.openUrl = url
        Singular.start(config)

        return true
    }

    // Add your getConfig() function here
    // MARK: - Singular Configuration
    private func getConfig() -> SingularConfig? {
        // ... (same as above)
    }

    // Add your handleDeeplink() function here
    // MARK: - Deep Link Handler
    private func handleDeeplink(_ params: SingularLinkParams) {
        // ... (same as above)
    }
}

インストールの確認

プリフライト チェックリスト

統合をビルドおよびテストする前に、以下の項目を確認してください。

  • CocoaPods、SPM、または手動フレームワークで SDK をインストール
  • Swift ブリッジングヘッダーを作成(Swift を使用する場合)
  • getConfig() 関数を実装
  • Singular.start(config) をすべてのエントリポイントで呼び出し
  • SDK Key と SDK Secret を設定に追加
  • ATT タイムアウトを設定(ATT プロンプトを表示する場合のみ)
  • ディープリンクハンドラーを設定(ディープリンクを使用する場合のみ)
  • アプリがエラーなくビルドされる

次のステップ:

  • アプリをビルドして実行
  • IDFV の print 文をコンソールで確認
  • IDFV を使って Singular SDK Console でテスト
  • 1〜2 分以内に SDK Console にセッションが表示されることを確認

オプション:App Tracking Transparency (ATT)

IDFA アクセスのためのユーザー許可を要求し、アトリビューションの精度を向上させるために ATT を設定します。

以下の場合は、このセクションをスキップしてください: アプリで ATT プロンプトを表示していない場合。

なぜ ATT 同意を要求するのか?

IDFA のメリット

iOS 14.5 以降、アプリはデバイスの IDFA(Identifier for Advertisers)にアクセスするためのユーザー許可を要求する必要があります。

IDFA ありとなしのアトリビューションの比較:

  • IDFA あり: デバイスレベルでの正確なアトリビューションと精度の高いインストールマッチング
  • IDFA なし: IP、ユーザーエージェント、デバイスフィンガープリンティングを使用した確率的アトリビューション

推奨: アトリビューションの精度を高めるため、ATT 同意を要求してください。Singular は IDFA なしでもアトリビューションが可能ですが、精度は低下します。


ATT 遅延の設定

SDK 初期化の遅延

最初のセッションを Singular に送信する前に、ユーザーの ATT 応答を待つためのタイムアウトを追加します。

重要: SDK は、最初のセッションを送信する前に ATT 同意を待つ必要があります。そうしないと、最初のアトリビューション イベントに IDFA が含まれません。

Swift Objective-C
func getSingularConfig() -> SingularConfig? {
    guard let config = SingularConfig(
        apiKey: "YOUR_SDK_KEY",
        andSecret: "YOUR_SDK_SECRET"
    ) else {
        return nil
    }

    // Wait up to 300 seconds for ATT response
    config.waitForTrackingAuthorizationWithTimeoutInterval = 300

    return config
}

ATT フローのタイムライン

ATT 遅延を設定した場合の動作は次のとおりです:

  1. アプリの起動: SDK はイベントの記録を開始しますが、まだ送信しません
  2. ATT プロンプト: アプリが ATT 同意ダイアログを表示
  3. ユーザーの応答: ユーザーが許可を付与または拒否
  4. SDK がデータを送信: SDK はキューに入れられたイベントを IDFA(許可された場合)と共にすぐに送信
  5. タイムアウト フォールバック: 応答なしで 300 秒経過すると、SDK は IDFA なしでデータを送信

ベストプラクティス: アトリビューションの IDFA 取得を最大化するため、ATT プロンプトはできるだけ早く(理想的にはアプリの初回起動時に)表示してください。