SDK de iOS - Integración básica

Documento

Requisitos previos

Complete los pasos en Integración de un SDK Singular: Planificación y requisitos previos antes de proceder con esta integración.

Importante: Estos pasos de prerrequisitos son necesarios para cualquier integración de Singular SDK.

Instalación

Elija el método de instalación que prefiera. Recomendamos CocoaPods para la mayoría de los proyectos.

Decisión rápida:

  • ¿Ya utiliza CocoaPods? Utilice el método 1
  • ¿Sólo proyecto SPM? Utilice el método 2
  • ¿Sin gestor de paquetes? Utilice 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. Inicializar Podfile (omitir si ya tienes uno):
    Terminal
    cd /path/to/your/project
    pod init
  2. Añade Singular SDK a tu Podfile:
    Podfile
    platform :ios, '12.0'
    
    target 'YourAppName' do
      use_frameworks!
      
      # Singular SDK
      pod 'Singular-SDK'
      
    end
  3. Instalar dependencias:
    Terminal
    pod install
  4. Abrir espacio de trabajo: A partir de ahora, abre .xcworkspace en lugar de .xcodeproj
  5. Sólo proyectos Swift: Crear cabecera puente(ver más abajo)
Método 2: Gestor de paquetes Swift
#

Método 2: Gestor de paquetes Swift

Pasos de instalación:

  1. En Xcode: Archivo → Añadir paquetes
  2. Introduce la URL del repositorio: https://github.com/singular-labs/Singular-iOS-SDK
  3. Selecciona la versión y haz clic en Añadir paquete
  4. Añade los frameworks necesarios:
    Ir a Build Phases → Link Binary with Librariesy añadir:
    • Vincular bibliotecas requeridas:
      Vaya a Build Phases → Link Binary With Librariesy añada:
      • Libsqlite3.0.tbd
      • SystemConfiguration.framework
      • Security.framework
      • Libz.tbd
      • AdSupport.framework
      • WebKit.framework
      • StoreKit.framework
      • AdServices.framework (marcar como Opcional)
  5. Sólo proyectos Swift: Crear cabecera puente(ver más abajo)
Método 3: Instalación manual del framework
#

Método 3: Instalación manual del framework

Cuándo utilizarlo: Utilice este método sólo si no puede utilizar CocoaPods o SPM.

Descargar Framework:

Pasos de instalación:

  1. Descomprimir el framework descargado
  2. En Xcode: Haz clic con el botón derecho del ratón en el proyecto → Añadir archivos a [Proyecto]
  3. Selecciona Crear grupos y añade la carpeta del framework
  4. Enlaza las librerías necesarias:
    Ve a Build Phases → Link Binary With Librariesy añade:
    • Libsqlite3.0.tbd
    • SystemConfiguration.framework
    • Security.framework
    • Libz.tbd
    • AdSupport.framework
    • WebKit.framework
    • StoreKit.framework
    • AdServices.framework (marcar como Opcional)
  5. Incrustar marco de trabajo:
    Vaya a General → Frameworks, bibliotecas y contenido incrustado.
    Establezca Singular framework en Incrustar y firmar

Cabecera de puente Swift

Importante: Necesario para proyectos Swift que utilicen CocoaPods o SPM.

  1. Cree el archivo de cabecera:
    Xcode → Archivo → Nuevo → Archivo → Archivo de cabecera.
    Nómbrelo YourProjectName-Bridging-Header.h
  2. Añade la importación:
    Bridging Header
    #import <Singular/Singular.h>
  3. Enlace en ajustes de compilación:
    Build Settings → Objective-C Bridging Header
    Establecer en: YourProjectName/YourProjectName-Bridging-Header.h

Configurar e inicializar SDK

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


Crear objeto de configuración

Configuración básica

Cree un objeto SingularConfig con sus credenciales del SDK y funciones opcionales. Esta configuración es universal para todas las arquitecturas de aplicaciones.

Obtenga sus credenciales: Encuentra tu SDK Key y SDK Secret en la plataforma Singular en Herramientas de Desarrollador → Integración SDK.

SwiftObjective-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 Auto-Enabled: A partir de la versión 12.0.6 del SDK, SKAdNetwork está habilitado por defecto. No es necesaria ninguna configuración adicional.


Inicializar SDK

Elija la arquitectura de su aplicación

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

¿Qué arquitectura tengo?

  • SceneDelegate: Comprueba si tu proyecto tiene SceneDelegate.swift o SceneDelegate.m
  • SwiftUI: Tu app comienza con @main struct YourApp: App
  • Sólo AppDelegate: Aplicaciones anteriores a iOS 13 o aplicaciones sin SceneDelegate

iOS moderno: SceneDelegate (iOS 13+)
#

iOS moderno: SceneDelegate (iOS 13+)

Dónde añadir código: SceneDelegate.swift o SceneDelegate.m

Puntos de entrada a inicializar:

  • willConnectTo session - Lanzamiento de la aplicación
  • continue userActivity - Enlaces universales
  • openURLContexts - Esquemas de enlaces profundos
SwiftObjective-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: SwiftUI App (iOS 14+)
#

iOS moderno: SwiftUI App (iOS 14+)

Dónde añadir el código: Tu archivo principal Appstruct

Puntos de entrada para inicializar:

  • .onOpenURL(of: scenePhase) - Manejar esquemas de URL personalizados
  • .onContinueUserActivity(of: scenePhase) - Manejar enlaces universales (enlaces profundos singulares)
  • .onChange.active - Maneja la inicialización en el primer lanzamiento si no ocurrió ningún enlace profundo. Maneja Deeplinks diferidos.
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 (Pre-iOS 13)
#

iOS heredado: AppDelegate (Pre-iOS 13)

Dónde añadir el código: AppDelegate.swift o AppDelegate.m

Puntos de entrada a inicializar:

  • didFinishLaunchingWithOptions - Lanzamiento de la aplicación
  • continue userActivity - Enlaces universales
  • open url - Esquemas de enlaces profundos
SwiftObjective-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 comprobación previa al vuelo

Confirme estos elementos antes de crear y probar su integración.

  • SDK instalado a través de CocoaPods, SPM o framework manual
  • Cabecera de puente Swift creada (si se utiliza Swift)
  • getConfig() función implementada
  • Singular.start(config) llamada en todos los puntos de entrada
  • SDK Key y SDK Secret añadidos a la configuración
  • Tiempo de espera ATT configurado (sólo si se muestra el mensaje ATT)
  • Gestor de enlaces profundos configurado (sólo si se utilizan enlaces profundos)
  • La aplicación se compila sin errores

Siguientes pasos:

  • Cree y ejecute su aplicación
  • Compruebe la consola para la declaración de impresión IDFV
  • Pruebe en Singular SDK Consoleutilizando su IDFV
  • Compruebe que las sesiones aparecen en SDK Console en 1-2 minutos

Opcional: App Tracking Transparency (ATT)

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

Omita esta sección si: No está mostrando un aviso ATT en su app.

¿Por qué solicitar el consentimiento ATT?

Ventajas del IDFA

A partir de iOS 14.5, las apps deben solicitar permiso al usuario para acceder al IDFA (Identificador para Anunciantes) del dispositivo.

Atribución con y sin IDFA:

  • Con IDFA
  • :
  • Atribución precisa a nivel de dispositivo y coincidencia exacta de instalación
  • Sin IDFA: Atribución probabilística utilizando IP, agente de usuario y huella digital de dispositivo

Recomendación: Solicitar el consentimiento de la ATT para mejorar la precisión de la atribución. Singular puede atribuir sin IDFA, pero la precisión disminuye.


Configurar Retraso ATT

Retraso Inicialización SDK

Añada un tiempo de espera para esperar la respuesta ATT del usuario antes de enviar la primera sesión a Singular.

Crítico: El SDK debe esperar el consentimiento ATT antes de enviar la primera sesión. De lo contrario, el evento de atribución inicial no incluirá el IDFA.

SwiftObjective-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 ATT

Esto es lo que ocurre cuando se configura un retraso ATT:

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

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