SDKs mobile

Swift (iOS)

SDK nativo do Paywallo para iOS e macOS. Swift Package Manager, async/await e os mesmos eventos do SDK React Native.

Requisitos

  • iOS 16 ou superior · macOS 12 ou superior
  • Swift 5.9 ou superior (Xcode 15+)
  • Capability In-App Purchase no target (o SDK usa StoreKit 2)
  • Instalação por Swift Package Manager

Usa React Native ou Expo? A instalação é outra — vá pra .

1

Instalar o pacote

No Xcode: File → Add Package Dependencies… e cole a URL do repositório. Use a regra Up to Next Major a partir de 2.6.0 e marque o produto PaywalloSDK no seu target.

text
https://github.com/Virex-Tech/paywallo-ios-sdk.git

Se o app é um pacote SwiftPM, declare no manifesto:

swift
// Package.swift
dependencies: [
    .package(url: "https://github.com/Virex-Tech/paywallo-ios-sdk.git", from: "2.6.0")
],
targets: [
    .target(
        name: "MeuApp",
        dependencies: [
            .product(name: "PaywalloSDK", package: "paywallo-ios-sdk")
        ]
    )
]

Não há CocoaPods nem Carthage: a distribuição é só por Swift Package Manager. Adicione também a capability In-App Purchase no target — o SDK usa StoreKit 2 pra processar e validar compras.

2

Inicializar o SDK

Não existe Provider como no React Native: você chama initialize uma vez, no início do ciclo de vida do app. É async throws e é seguro chamar de novo — a segunda chamada espera a primeira em vez de reinicializar. A appKey vem do dashboard, na aba SDK.

Resolva o ATT antes de inicializar (iOS)

Peça a permissão de App Tracking Transparency (ATT) e espere o usuário responder antes de chamar initialize. Se o SDK subir primeiro, ele inicializa com attStatus=undetermined, sem IDFA e sem madid no CAPI, o que degrada a atribuição do install. Timing e copy do prompt são responsabilidade do app e decidem a taxa de opt-in: medido nos nossos clientes, o Fitcal converte 42,0% contra 1,8% do PeptPro, 23x de variação. Requer NSUserTrackingUsageDescription no Info.plist.

Dá pra deixar o SDK pedir o ATT por você com requestATT: true, mas aí o prompt aparece no boot, que costuma ser o pior momento possível. O caminho recomendado é pedir na sua própria tela e só depois inicializar:

swift
import AppTrackingTransparency
import PaywalloSDK

func bootstrap() async {
    // 1. Pede o ATT e ESPERA o usuário responder
    _ = await ATTrackingManager.requestTrackingAuthorization()

    // 2. Só agora inicializa o SDK — o IDFA já está resolvido
    try? await PaywalloClient.shared.initialize(config)
}

Abaixo, os campos que valem a pena configurar em PaywalloInitConfig. Só appKey é obrigatório:

swift
import PaywalloSDK
import SwiftUI

@main
struct MeuApp: App {
    init() {
        Task {
            do {
                try await PaywalloClient.shared.initialize(
                    PaywalloInitConfig(
                        // Obrigatório: obtido no dashboard, aba SDK
                        appKey: "pk_xxxxxxxx",
                        // Logs do SDK no console. Deixe false em release
                        debug: false,
                        // .production (padrão) ou .sandbox pra testar IAP sem cobrar
                        environment: .production,
                        // Sessão automática por foreground/background (padrão: true)
                        autoStartSession: true,
                        // Dispara o prompt de ATT dentro do init. Ver aviso acima
                        requestATT: false,
                        // Textos da tela de erro do paywall (todos opcionais)
                        errorStrings: PaywallErrorStrings(
                            title: "Ops",
                            retry: "Tentar novamente",
                            close: "Fechar"
                        ),
                        // Placement pré-carregado no boot: present fica quase instantâneo
                        autoPreloadCampaign: "onboarding",
                        // Push notifications via APNs (padrão: true)
                        notifications: true,
                        // Erros do SDK em runtime, pra logar no seu crash reporter
                        onError: { error in print("[Paywallo]", error) }
                    )
                )
            } catch {
                // O SDK já tenta 3 vezes sozinho (2s, 5s, 10s) antes de chegar aqui
                print("[Paywallo] init falhou:", error)
            }
        }
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

O SDK dispara $app_installed sozinho durante o init, não chame na mão. Mas disparar sozinho não é acertar sozinho: a qualidade da atribuição desse evento depende do ATT já estar resolvido nesse momento.

3

Registrar o presenter do paywall

No iOS nativo o SDK não desenha o paywall por você — quem apresenta a UI é o app. Sem um presenter registrado, presentPaywall retorna presented: false e nada aparece na tela. Registre uma vez, no boot:

swift
// No boot, depois do initialize:
PaywalloClient.shared.registerPaywallPresenter { config, products in
    // Sua UI: apresente o paywall e só retorne quando o usuário decidir.
    // O SDK entrega config (layout do dashboard) e os produtos já carregados.
    let outcome = await MinhaTelaDePaywall.apresentar(config: config, products: products)

    return PaywallResult(
        presented: true,
        purchased: outcome.comprou,
        productId: outcome.productId,
        transactionId: outcome.transactionId,
        cancelled: outcome.fechou,
        restored: outcome.restaurou
    )
}

O SDK entrega as peças prontas pra montar essa tela: PaywallPresenter (um UIViewController que hospeda a WebView do layout feito no dashboard), PaywalloWebView, PaywallTracking e StoreKitManager pra compra. Pra campanhas com teste A/B, o equivalente é registerCampaignPresenter.

Verificar que funcionou

Ligue debug: true no config e rode o app. Com o SDK de pé, o console mostra as chamadas de rede com o prefixo [Paywallo:Api]. Em código, isReady() responde se o init já terminou.

Chamadas no boot: use waitUntilReady() antes de identify e track no cold start. Sem isso, identify lança ClientError(NOT_INITIALIZED) se o init nem começou.

swift
// No boot, antes de qualquer identify/track:
await PaywalloClient.shared.waitUntilReady()

try await PaywalloClient.shared.identify(
    IdentifyOptions(
        email: user.email,
        // userId é obrigatório pra linkar o device ao usuário
        properties: ["userId": AnyCodable(user.id)]
    )
)

PaywalloClient.shared.track("app_pronto")

print("distinctId:", PaywalloClient.shared.getDistinctId())

Se o evento não aparecer no dashboard em alguns segundos, confira nesta ordem: appKey do app certo, environment batendo com o filtro do painel, e o device com rede — o SDK tem fila offline durável e só entrega quando a conexão volta. getOfflineQueueSize() mostra quantos eventos estão presos.

Identificar o usuário

identify é o que liga o device a uma pessoa e o que enriquece os eventos enviados pro CAPI do Meta e do TikTok. Quanto mais campos, melhor a qualidade da correspondência. Valores livres em properties vão embrulhados em AnyCodable.

swift
try await PaywalloClient.shared.identify(
    IdentifyOptions(
        email: "user@email.com",
        properties: [
            "userId": AnyCodable(user.id),
            "plano": AnyCodable("free")
        ],
        phone: "+5511999999999",
        firstName: "Maria",
        lastName: "Silva",
        dateOfBirth: "1995-03-14",
        gender: .female
    )
)

// Logout: novo anônimo, SDK continua pronto
await PaywalloClient.shared.reset()

Chamar identify antes do init terminar não perde dado: o SDK enfileira e reaplica quando fica pronto. O que cada campo faz na atribuição está em .

Enviar eventos

track não é async e não lança: só enfileira no batcher, que entrega em background e sobrevive a app fechado. Use priority: .critical pro que não pode atrasar.

swift
// Simples
PaywalloClient.shared.track("tela_aberta")

// Com propriedades
PaywalloClient.shared.track(
    "receita_favoritada",
    properties: ["receitaId": AnyCodable(42), "origem": AnyCodable("busca")]
)

// Crítico: fura a fila do batch e vai na frente
PaywalloClient.shared.track("checkout_iniciado", priority: .critical)

A lista de eventos automáticos e as convenções de nome estão em .

Exibir paywalls e campanhas

Com o presenter registrado, apresentar é uma linha. Pré-carregar a campanha no boot (ou via autoPreloadCampaign) faz a primeira exibição sair sem espera de rede.

swift
// Paywall direto por placement
let result = await PaywalloClient.shared.presentPaywall(placement: "onboarding")

if result.purchased || result.restored {
    liberarConteudoPremium()
}

// Campanha (teste A/B de paywall configurado no dashboard)
await PaywalloClient.shared.preloadCampaign(placement: "onboarding")
let campaign = await PaywalloClient.shared.presentCampaign(placement: "onboarding")

Assinatura e restauração

A compra em si acontece no seu presenter, via StoreKit 2. O que o client expõe é o estado: consulta de assinatura, gate de conteúdo e restore.

swift
// Checagem rápida (usa cache)
let ativo = await PaywalloClient.shared.hasActiveSubscription()

// Detalhe da assinatura
if let sub = await PaywalloClient.shared.getSubscription(forceRefresh: true) {
    print(sub.productId, sub.status, sub.expiresAt as Any)
}

// Gate: mostra o paywall se não tiver assinatura e devolve o resultado
let liberado = await PaywalloClient.shared.requireSubscription(paywallPlacement: "premium")

// Botão de restaurar compras
let restore = try await PaywalloClient.shared.restorePurchases()
print(restore.success, restore.restoredProducts)

A validação é server-side e depende das credenciais da . Sem isso, a assinatura não aparece no painel nem chega ao CAPI.

Onboarding

Cada step vira uma etapa do funil de onboarding no dashboard. order define a posição e timeOnPrevS mede o tempo gasto na tela anterior.

swift
try await PaywalloClient.shared.onboardingStep(stepName: "objetivo", order: 1)
try await PaywalloClient.shared.onboardingStep(stepName: "peso", order: 2, timeOnPrevS: 4.2)
try await PaywalloClient.shared.onboardingComplete()

Como o funil é montado e onde ver o dropout: .

Push notifications (APNs)

Ligado por padrão (notifications: true). Você pede a permissão quando fizer sentido no seu fluxo e repassa o token do APNs pro SDK.

swift
// 1. Permissão (o SDK registra o device no Paywallo sozinho)
let status = await PaywalloClient.shared.requestPushPermission()
guard status == .granted || status == .provisional else { return }

// 2. No AppDelegate, repasse o token do APNs
func application(
    _ application: UIApplication,
    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
    let token = deviceToken.map { String(format: "%02x", $0) }.joined()
    Task { await PaywalloClient.shared.setApnsToken(token) }
}

Superwall

A ponte com o Superwall não liga sozinha no iOS nativo: é preciso chamar startSuperwallBridge depois do init. Ela espelha os eventos do Superwall pro Paywallo e empurra os atributos de atribuição pw_* pro Superwall, que é o que permite segmentar audiência por origem.

swift
import PaywalloSDK
import SuperwallKit

// Depois do initialize:
startSuperwallBridge(attribution: AttributionTracker())

Sem o SuperwallKit linkado no projeto, a função existe e não faz nada. A configuração do lado do painel está em . Já a integração com o Meta (FBSDKCoreKit) é diferente: essa liga sozinha, basta ter o SDK do Meta no projeto.

Diferenças em relação ao SDK React Native

  • Não existe Provider: você chama initialize() e pronto.
  • presentPaywall exige registerPaywallPresenter antes — a UI é montada pelo app.
  • Não existe purchase() no client. A compra roda no seu presenter, via StoreKit 2.
  • Não existe setCurrentLanguage/setDefaultLanguage: o idioma vem sempre do dispositivo.
  • Não existe conversion value de SKAdNetwork. Se o app precisa disso hoje, é o SDK React Native ou implementação própria.
  • A ponte do Superwall é manual (startSuperwallBridge); no React Native ela é automática.

Essa página foi útil?

Desenvolvido e mantido por Virex