SDK do iOS - Integração básica


Pré-requisitos

Conclua as etapas em Integrando um SDK do Singular: Planejamento e Pré-requisitos antes de prosseguir com esta integração.

Importante: Estas etapas de pré-requisito são obrigatórias para qualquer integração do SDK do Singular.

Configuração específica do iOS

Antes de inicializar o SDK, confirme se os itens a seguir estão configurados no seu projeto Xcode. A falta de qualquer um destes itens é uma causa comum de falhas silenciosas do SDK.

Entradas no Info.plist:

  • NSUserTrackingUsageDescription — Uma string voltada ao usuário explicando por que seu app solicita rastreamento. Exigida pela Apple se você exibir o prompt de App Tracking Transparency (ATT) ou ler o IDFA. O SDK do Singular respeita o ATT automaticamente; sem esta chave o SDK continua funcionando, mas não consegue acessar o IDFA no iOS 14.5+.
  • URL Types e CFBundleURLSchemes — Obrigatórios se sua estratégia de deep link usar esquemas de URI personalizados (ex.: myapp:// ). Não são necessários para Universal Links / Singular Links via HTTPS.

Entitlements (Signing & Capabilities):

  • Associated Domains — Adicione uma entrada por domínio com a marca Singular no formato applinks:yourcompany.sng.link . Obrigatório para atribuição via Universal Link / Singular Link. Este domínio hospedado pela Singular irá hospedar um arquivo JSON apple-app-site-association (AASA) que a Apple baixa no momento da instalação. O arquivo AASA é gerado e atualizado quando links são criados para o app na Plataforma Singular.
  • Push Notifications — Obrigatórias para rastreamento de desinstalação e re-engajamento atribuído por push. Consulte os artigos sobre Suporte a Push Notifications e Rastreamento de Desinstalação.

Frameworks do sistema obrigatórios (somente para integração manual — CocoaPods e SwiftPM os vinculam automaticamente):

  • StoreKit.framework — IAP e SKAdNetwork.
  • AdServices.framework — Token de atribuição do Apple Search Ads (iOS 14.3+).
  • AppTrackingTransparency.framework — Prompt do ATT e status de autorização (iOS 14.5+).
  • UserNotifications.framework — Push notifications.
  • WebKit.framework — Obrigatório somente se você usar a ponte JS WKWebView do Singular.

Instalação

Escolha seu método de instalação preferido. Recomendamos o CocoaPods para a maioria dos projetos.

Decisão rápida:

  • Já usa CocoaPods? Use o Método 1
  • Projeto somente SPM? Use o Método 2
  • Sem gerenciador de pacotes? Use o Método 3

Métodos de instalação

Método 1: CocoaPods (Recomendado)

Método 1: CocoaPods (Recomendado)

Requisitos:

Etapas de instalação:

  1. Inicialize o Podfile (pule se já tiver um):

    Terminal
    cd /path/to/your/project
    pod init
  2. Adicione o SDK do Singular ao seu Podfile:

    Podfile
    platform :ios, '12.0'
    
    target 'YourAppName' do
      use_frameworks!
    
      # Singular SDK
      pod 'Singular-SDK'
    
    end
  3. Instale as dependências:

    Terminal
    pod install
  4. Abra o workspace: A partir de agora, abra .xcworkspace em vez de .xcodeproj
  5. Somente para projetos Swift: Crie o bridging header (veja abaixo)
Método 2: Swift Package Manager

Método 2: Swift Package Manager

Etapas de instalação:

  1. No Xcode: File → Add Packages
  2. Informe a URL do repositório: https://github.com/singular-labs/Singular-iOS-SDK
  3. Selecione a versão e clique em Add Package
  4. Adicione os frameworks obrigatórios:
    Vá em Build Phases → Link Binary with Libraries e adicione:
    • Vincule as bibliotecas obrigatórias:
      Vá em Build Phases → Link Binary With Libraries e adicione:
      • Libsqlite3.0.tbd
      • SystemConfiguration.framework
      • Security.framework
      • Libz.tbd
      • AdSupport.framework
      • WebKit.framework
      • StoreKit.framework
      • AdServices.framework (marque como Optional)
  5. Somente para projetos Swift: Crie o bridging header (veja abaixo)
Método 3: Instalação manual do framework

Método 3: Instalação manual do framework

Quando usar: Use este método apenas se não for possível usar CocoaPods ou SPM.

Baixar o framework:

Etapas de instalação:

  1. Descompacte o framework baixado
  2. No Xcode: Clique com o botão direito no projeto → Add Files To [Project]
  3. Selecione Create Groups e adicione a pasta do framework
  4. Vincule as bibliotecas obrigatórias:
    Vá em Build Phases → Link Binary With Libraries e adicione:
    • Libsqlite3.0.tbd
    • SystemConfiguration.framework
    • Security.framework
    • Libz.tbd
    • AdSupport.framework
    • WebKit.framework
    • StoreKit.framework
    • AdServices.framework (marque como Optional)
  5. Incorpore o framework:
    Vá em General → Frameworks, Libraries, and Embedded Content
    Defina o framework Singular como Embed & Sign

Bridging Header do Swift

Importante: Obrigatório para projetos Swift que usam CocoaPods ou SPM.

  1. Crie o arquivo de header:
    Xcode → File → New → File → Header File
    Nomeie como YourProjectName-Bridging-Header.h
  2. Adicione o import:

    Bridging Header
    #import <Singular/Singular.h>
  3. Vincule nas configurações de build:
    Build Settings → Objective-C Bridging Header
    Defina como: YourProjectName/YourProjectName-Bridging-Header.h

Configurar e inicializar o SDK

Crie um objeto de configuração e inicialize o SDK nos pontos de entrada do seu app.


Criar o objeto de configuração

Configuração básica

Crie um objeto SingularConfig com suas credenciais do SDK e recursos opcionais. Esta configuração é universal para todas as arquiteturas de app.

Obtenha suas credenciais: Encontre sua SDK Key e SDK Secret na plataforma Singular em 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 ativado automaticamente: A partir da versão 12.0.6 do SDK, o SKAdNetwork é ativado por padrão. Nenhuma configuração adicional é necessária.


Inicializar o SDK

Escolha a arquitetura do seu app

Inicialize o SDK em todos os pontos de entrada do app. O padrão de inicialização depende da arquitetura do seu app.

Qual arquitetura eu tenho?

  • SceneDelegate: Verifique se seu projeto tem SceneDelegate.swift ou SceneDelegate.m
  • SwiftUI: Seu app começa com @main struct YourApp: App
  • Somente AppDelegate: Apps pré-iOS 13 ou apps sem SceneDelegate

iOS moderno: SceneDelegate (iOS 13+)

iOS moderno: SceneDelegate (iOS 13+)

Onde adicionar o código: SceneDelegate.swift ou SceneDelegate.m

Pontos de entrada para inicializar:

  • willConnectTo session - Inicialização do app
  • 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: App SwiftUI (iOS 14+)

iOS moderno: App SwiftUI (iOS 14+)

Onde adicionar o código: Seu arquivo struct App principal

Pontos de entrada para inicializar:

  • .onOpenURL(of: scenePhase) - Tratar esquemas de URL personalizados
  • .onContinueUserActivity(of: scenePhase) - Tratar Universal Links (Singular Deep Links)
  • .onChange.active - Tratar a inicialização na primeira execução se nenhum deep link tiver ocorrido. Trata 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 legado: AppDelegate (Pré-iOS 13)

iOS legado: AppDelegate (Pré-iOS 13)

Onde adicionar o código: AppDelegate.swift ou AppDelegate.m

Pontos de entrada para inicializar:

  • didFinishLaunchingWithOptions - Inicialização do app
  • 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 a instalação

Checklist pré-execução

Confirme estes itens antes de compilar e testar sua integração.

  • SDK instalado via CocoaPods, SPM ou framework manual
  • Bridging header do Swift criado (se estiver usando Swift)
  • Função getConfig() implementada
  • Singular.start(config) chamado em todos os pontos de entrada
  • SDK Key e SDK Secret adicionados à configuração
  • Timeout do ATT configurado (somente se exibir o prompt do ATT)
  • Handler de deep link configurado (somente se estiver usando deep links)
  • O app compila sem erros

Próximas etapas:

  • Compile e execute seu app
  • Verifique no console o print do IDFV
  • Teste no Singular SDK Console usando seu IDFV
  • Verifique se as sessões aparecem no SDK Console em 1 a 2 minutos

Opcional: App Tracking Transparency (ATT)

Configure o ATT para solicitar permissão do usuário para acessar o IDFA e melhorar a precisão da atribuição.

Pule esta seção se: Você não estiver exibindo um prompt do ATT no seu app.

Por que solicitar o consentimento do ATT?

Benefícios do IDFA

A partir do iOS 14.5, os apps precisam solicitar permissão do usuário para acessar o IDFA (Identifier for Advertisers) do dispositivo.

Atribuição com vs. sem IDFA:

  • Com IDFA: Atribuição precisa em nível de dispositivo e correspondência de instalações com precisão
  • Sem IDFA: Atribuição probabilística usando IP, user agent e fingerprinting de dispositivo

Recomendação: Solicite o consentimento do ATT para obter melhor precisão de atribuição. A Singular consegue atribuir sem o IDFA, mas a precisão diminui.


Configurar o atraso do ATT

Atrasar a inicialização do SDK

Adicione um timeout para aguardar a resposta do ATT do usuário antes de enviar a primeira sessão para a Singular.

Crítico: O SDK precisa aguardar o consentimento do ATT antes de enviar a primeira sessão. Caso contrário, o evento inicial de atribuição não incluirá o 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
}

Linha do tempo do fluxo ATT

Veja o que acontece quando você configura um atraso para o ATT:

  1. Inicialização do app: O SDK começa a registrar eventos, mas ainda não os envia
  2. Prompt do ATT: Seu app exibe a caixa de diálogo de consentimento do ATT
  3. Resposta do usuário: O usuário concede ou nega a permissão
  4. O SDK envia os dados: O SDK envia imediatamente os eventos enfileirados com o IDFA (se concedido)
  5. Fallback de timeout: Se 300 segundos se passarem sem resposta, o SDK envia os dados mesmo assim

Melhor prática: Exiba o prompt do ATT o mais cedo possível (idealmente na primeira inicialização do app) para maximizar a disponibilidade do IDFA para atribuição.