SDK de iOS - Integración básica


Requisitos previos

Completa los pasos en Integrar un SDK de Singular: Planificación y requisitos previos antes de continuar con esta integración.

Importante: Estos pasos previos son obligatorios para cualquier integración del SDK de Singular.

Configuración específica para iOS

Antes de inicializar el SDK, confirma que los siguientes elementos estén configurados en tu proyecto de Xcode. La falta de cualquiera de ellos es una causa común de fallos silenciosos del SDK.

Entradas en Info.plist:

  • NSUserTrackingUsageDescription — Una cadena visible para el usuario que explica por qué tu aplicación solicita tracking. Requerida por Apple si alguna vez presentas el aviso de App Tracking Transparency (ATT) o lees el IDFA. El SDK de Singular respeta ATT automáticamente; sin esta clave el SDK sigue funcionando, pero no puede acceder al IDFA en iOS 14.5+.
  • URL Types y CFBundleURLSchemes — Requeridos si tu estrategia de deep linking usa esquemas URI personalizados (por ejemplo, myapp:// ). No son necesarios para Universal Links / Singular Links vía HTTPS.

Entitlements (Signing & Capabilities):

  • Associated Domains — Agrega una entrada por cada dominio de marca Singular con el formato applinks:yourcompany.sng.link . Requerido para la atribución de Universal Link / Singular Link. Este dominio alojado por Singular hospedará un archivo JSON apple-app-site-association (AASA) que Apple descarga en el momento de la instalación. El archivo AASA se genera y se actualiza cuando se crean enlaces para la aplicación en la plataforma de Singular.
  • Push Notifications — Requerido para el tracking de desinstalaciones y la re-interacción atribuida vía push. Consulta los artículos sobre Supporting Push Notifications y Uninstall Tracking.

Frameworks del sistema requeridos (solo para integración manual — CocoaPods y SwiftPM los enlazan automáticamente):

  • StoreKit.framework — IAP y SKAdNetwork.
  • AdServices.framework — Token de atribución de Apple Search Ads (iOS 14.3+).
  • AppTrackingTransparency.framework — Aviso de ATT y estado de autorización (iOS 14.5+).
  • UserNotifications.framework — Notificaciones push.
  • WebKit.framework — Requerido solo si usas el puente JS de WKWebView de Singular.

Instalación

Elige tu método de instalación preferido. Recomendamos CocoaPods para la mayoría de los proyectos.

Decisión rápida:

  • ¿Ya usas CocoaPods? Usa el Método 1
  • ¿Proyecto solo con SPM? Usa el Método 2
  • ¿Sin gestor de paquetes? Usa el Método 3

Métodos de instalación

Método 1: CocoaPods (recomendado)

Método 1: CocoaPods (recomendado)

Requisitos:

Pasos de instalación:

  1. Inicializa el Podfile (omite este paso si ya tienes uno):

    Terminal
    cd /path/to/your/project
    pod init
  2. Agrega el SDK de Singular a tu Podfile:

    Podfile
    platform :ios, '12.0'
    
    target 'YourAppName' do
      use_frameworks!
    
      # Singular SDK
      pod 'Singular-SDK'
    
    end
  3. Instala las dependencias:

    Terminal
    pod install
  4. Abre el workspace: A partir de ahora, abre .xcworkspace en lugar de .xcodeproj
  5. Solo proyectos Swift: Crea un bridging header (consulta más abajo)
Método 2: Swift Package Manager

Método 2: Swift Package Manager

Pasos de instalación:

  1. En Xcode: File → Add Packages
  2. Ingresa la URL del repositorio: https://github.com/singular-labs/Singular-iOS-SDK
  3. Selecciona la versión y haz clic en Add Package
  4. Agrega los frameworks requeridos:
    Ve a Build Phases → Link Binary with Libraries y agrega:
    • Enlaza las librerías requeridas:
      Ve a Build Phases → Link Binary With Libraries y agrega:
      • Libsqlite3.0.tbd
      • SystemConfiguration.framework
      • Security.framework
      • Libz.tbd
      • AdSupport.framework
      • WebKit.framework
      • StoreKit.framework
      • AdServices.framework (marcar como Optional)
  5. Solo proyectos Swift: Crea un bridging header (consulta más abajo)
Método 3: Instalación manual del framework

Método 3: Instalación manual del framework

Cuándo usarlo: Usa este método solo si no puedes usar CocoaPods o SPM.

Descargar framework:

Pasos de instalación:

  1. Descomprime el framework descargado
  2. En Xcode: clic derecho en el proyecto → Add Files To [Project]
  3. Selecciona Create Groups y agrega la carpeta del framework
  4. Enlaza las librerías requeridas:
    Ve a Build Phases → Link Binary With Libraries y agrega:
    • Libsqlite3.0.tbd
    • SystemConfiguration.framework
    • Security.framework
    • Libz.tbd
    • AdSupport.framework
    • WebKit.framework
    • StoreKit.framework
    • AdServices.framework (marcar como Optional)
  5. Incrusta el framework:
    Ve a General → Frameworks, Libraries, and Embedded Content
    Configura el framework Singular como Embed & Sign

Bridging Header de Swift

Importante: Requerido para proyectos Swift que usen CocoaPods o SPM.

  1. Crea el archivo header:
    Xcode → File → New → File → Header File
    Nómbralo YourProjectName-Bridging-Header.h
  2. Agrega el import:

    Bridging Header
    #import <Singular/Singular.h>
  3. Enlázalo en los build settings:
    Build Settings → Objective-C Bridging Header
    Configúralo como: YourProjectName/YourProjectName-Bridging-Header.h

Configurar e inicializar el SDK

Crea un objeto de configuración e inicializa el SDK en los puntos de entrada de tu aplicación.


Crear el objeto de configuración

Configuración básica

Crea un objeto SingularConfig con tus credenciales del SDK y funciones opcionales. Esta configuración es universal para todas las arquitecturas de aplicación.

Obtén tus credenciales: Encuentra tu SDK Key y SDK Secret en la plataforma de Singular en Developer Tools → SDK Integration .

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 habilitado automáticamente: A partir de la versión 12.0.6 del SDK, SKAdNetwork está habilitado de forma predeterminada. No se necesita configuración adicional.


Inicializar el SDK

Elige la arquitectura de tu aplicación

Inicializa el SDK en cada punto de entrada de la aplicación. El patrón de inicialización depende de la arquitectura de tu aplicación.

¿Qué arquitectura tengo?

  • SceneDelegate: Verifica si tu proyecto tiene SceneDelegate.swift o SceneDelegate.m
  • SwiftUI: Tu aplicación comienza con @main struct YourApp: App
  • Solo AppDelegate: Aplicaciones previas a iOS 13 o aplicaciones sin SceneDelegate

iOS moderno: SceneDelegate (iOS 13+)

iOS moderno: SceneDelegate (iOS 13+)

Dónde agregar el código: SceneDelegate.swift o SceneDelegate.m

Puntos de entrada para inicializar:

  • willConnectTo session - Lanzamiento de la aplicación
  • continue userActivity - Universal Links
  • openURLContexts - Esquemas de deep link
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 moderno: aplicación SwiftUI (iOS 14+)

iOS moderno: aplicación SwiftUI (iOS 14+)

Dónde agregar el código: El archivo de tu struct principal App

Puntos de entrada para inicializar:

  • .onOpenURL(of: scenePhase) - Manejar esquemas URL personalizados
  • .onContinueUserActivity(of: scenePhase) - Manejar Universal Links (Singular Deep Links)
  • .onChange.active - Manejar la inicialización en el primer lanzamiento si no hubo deep link. Gestiona los 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 heredado: AppDelegate (anterior a iOS 13)

iOS heredado: AppDelegate (anterior a iOS 13)

Dónde agregar el código: AppDelegate.swift o AppDelegate.m

Puntos de entrada para inicializar:

  • didFinishLaunchingWithOptions - Lanzamiento de la aplicación
  • continue userActivity - Universal Links
  • open url - Esquemas de deep link
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)
    }
}

Verificar la instalación

Lista de verificación previa

Confirma estos elementos antes de compilar y probar tu integración.

  • SDK instalado vía CocoaPods, SPM o framework manual
  • Bridging header de Swift creado (si usas Swift)
  • Función getConfig() implementada
  • Singular.start(config) llamado en todos los puntos de entrada
  • SDK Key y SDK Secret agregados a la configuración
  • Timeout de ATT configurado (solo si se muestra el aviso de ATT)
  • Manejador de deep link configurado (solo si usas deep links)
  • La aplicación compila sin errores

Siguientes pasos:

  • Compila y ejecuta tu aplicación
  • Revisa la consola para ver la sentencia print del IDFV
  • Prueba en la Singular SDK Console usando tu IDFV
  • Verifica que las sesiones aparezcan en la SDK Console en 1-2 minutos

Opcional: App Tracking Transparency (ATT)

Configura ATT para solicitar permiso al usuario para acceder al IDFA y mejorar la precisión de la atribución.

Omite esta sección si: No vas a mostrar un aviso de ATT en tu aplicación.

¿Por qué solicitar el consentimiento ATT?

Beneficios del IDFA

A partir de iOS 14.5, las aplicaciones deben solicitar permiso al usuario para acceder al IDFA (Identifier for Advertisers) del dispositivo.

Atribución con vs. sin IDFA:

  • Con IDFA: Atribución precisa a nivel de dispositivo y matching exacto de instalaciones
  • Sin IDFA: Atribución probabilística usando IP, user agent y fingerprinting de dispositivo

Recomendación: Solicita el consentimiento ATT para obtener mejor precisión en la atribución. Singular puede atribuir sin IDFA, pero la precisión disminuye.


Configurar el retraso de ATT

Retrasar la inicialización del SDK

Agrega un timeout para esperar la respuesta de ATT del usuario antes de enviar la primera sesión a Singular.

Crítico: El SDK debe esperar el consentimiento de ATT antes de enviar la primera sesión. De lo contrario, el evento inicial de atribución no incluirá el 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
}

Cronología del flujo de ATT

Esto es lo que sucede cuando configuras un retraso de ATT:

  1. Lanzamiento de la aplicación: El SDK comienza a registrar eventos, pero aún no los envía
  2. Aviso de ATT: Tu aplicación muestra el diálogo de consentimiento de ATT
  3. Respuesta del usuario: El usuario otorga o niega el permiso
  4. El SDK envía los datos: El SDK envía inmediatamente los eventos en cola con IDFA (si se otorgó)
  5. Tiempo de espera de respaldo: Si pasan 300 segundos sin respuesta, el SDK envía los datos de todos modos

Mejor práctica: Muestra el aviso de ATT lo antes posible (idealmente en el primer lanzamiento de la aplicación) para maximizar la disponibilidad del IDFA para la atribución.