Developer Docs / iOS SDK
GrowthCat iOS SDK
A Swift Package for referral redemption, self-promotion ads, sponsorship rendering, and feedback. Use the built-in SwiftUI components for the fastest integration or build completely custom UI with the render-data APIs.
Built For
Referral, ad, sponsor, and feedback experiences on iOS.
Includes
Built-in SwiftUI views plus headless APIs for custom product UI.
Not Included
Campaign management, referral creation, payouts, dashboards, or attribution reporting UI.
1. Overview
GrowthCat provides drop-in SwiftUI views and async headless APIs for referral redemption, ads, sponsorships, and native customer feedback. Use the built-in components for the shortest path or preserve the SDK's delivery and tracking semantics while rendering a completely custom experience.
Included
- Referral redemption with RevenueCat entitlement verification.
- Built-in and custom banner, interstitial, and rewarded ads.
- Live, available, and empty sponsor placements.
- Native feedback boards, submissions, voting, statuses, and comments.
Feature guardrails
- RevenueCat is required only for referral redemption.
- Rewarded ads require an app user ID and server validation.
- Custom ads and sponsors require measured viewability and disclosure.
- Feedback identity must be cleared after sign-out.
Choose the smallest integration surface
GrowthCatReferralViewfor referral redemption.GrowthCatAdVieworGrowthCatAdBannerViewfor ads.GrowthCatSponsorBannerViewfor sponsorships.presentFeedbackSheetfor the native feedback experience.
2. Requirements
- iOS 17+
- Swift 6.2+
- RevenueCat 5+ when using referral redemption
- GrowthCat app SDK key (`gc_live_...`)
- GrowthCat SDK installed and initialized in your app
To get started, integrate the SDK into your app and complete the client-side configuration. No backend work is required.
3. Production readiness checklist
- GrowthCat is initialized once at app startup.
- RevenueCat is configured first when referral redemption is enabled.
- `verificationEntitlementID` matches your RevenueCat entitlement exactly.
- `.growthCatOnSuccess` is the only unlock point for premium access.
- `.growthCatOnError` is treated as pending and reconciled later.
- Referral entry point exists in a persistent surface such as settings, paywall, or onboarding.
- Debug logging is disabled in Release.
- Consent maps to the intended measurement mode and privacy disclosures match enabled features.
4. Workspace selection
The SDK chooses the workspace itself and sends it in the X-GrowthCat-Workspace header on every request.
- By default,
DEBUGbuilds usesandbox. - Release builds use
live. - If you pass
environmentMode: .production, the SDK always useslive, even in aDEBUGbuild. - The header value is always either
sandboxorlive.
5. Installation
Add the package in Xcode via Swift Package Manager:
https://github.com/lapreamarcelo/GrowthCatSDK-iosThen import the module where you need it:
import GrowthCatUse the GrowthCat iOS integration skill
The SDK repository includes a Codex skill that inspects the app's pinned Swift package revision, reads its matching documentation and public declarations, and integrates referrals, ads, sponsorships, feedback, or privacy configuration using the app's existing architecture.
Install the skill from
https://github.com/lapreamarcelo/GrowthCatSDK-ios/tree/main/skills/growthcat-integrationAfter installation, ask Codex for the feature and screen you want:
Use $growthcat-integration to add native feedback to my Settings screen.6. Initialize SDK
Initialize GrowthCat once in App.init(), before any SDK view or service call. Configure RevenueCat first only when referral redemption is enabled.
public enum GrowthCatEnvironmentMode: Sendable {
case automatic
case production
}public static func initialize(
apiKey: String,
environmentMode: GrowthCatEnvironmentMode = .automatic,
logsEnabled: Bool = false,
baseURL: URL? = nil,
measurementMode: GrowthCatMeasurementMode
)There are two practical modes for initialization:
- Use
.automaticfor the default behavior. - Set
environmentMode: .productionto always use the live workspace.
Default behavior:
import SwiftUI
import GrowthCat
@main
struct DemoApp: App {
init() {
GrowthCat.initialize(
apiKey: "gc_live_123456",
environmentMode: .automatic,
logsEnabled: false,
measurementMode: .essential
)
}
var body: some Scene {
WindowGroup { ContentView() }
}
}In a DEBUG build this resolves to sandbox. In a release build this resolves to live.
Force production from a debug build:
GrowthCat.initialize(
apiKey: "gc_live_123456",
environmentMode: .production,
logsEnabled: false
)There is no public force-sandbox override. That is intentional, because it is too easy to leave behind accidentally.
Privacy measurement
GrowthCat defaults to .essential. Use .analytics only after your app establishes the appropriate consent or other legal basis, and apply consent changes immediately at runtime.
GrowthCat.setMeasurementMode(.analytics)
// Revocation clears optional queued events and rotates the analytics session.
GrowthCat.setMeasurementMode(.essential).essential: delivery, payment integrity, rewards, fraud prevention, and operations..analytics: essential measurement plus pseudonymous product analytics..disabled: stops measurement where it is not required for an explicit operation.
GrowthCat does not use IDFA or request App Tracking Transparency permission. Do not addNSUserTrackingUsageDescription solely because GrowthCat is installed.
7. SDK config feature flags
GrowthCat stores the latest backend-provided SDK flags so your app can decide whether to show redeem UI or allow redeem actions. The SDK does not enforce these flags automatically.
if let sdkConfig = GrowthCat.shared.sdkConfig {
if sdkConfig.showCodeRedeemUI {
// Show your GrowthCat entry point.
}
if sdkConfig.allowCodeRedeemExecution {
// Let the user start the redeem flow.
}
}
if let readiness = GrowthCat.shared.sdkReadiness {
print("Can validate codes: \(readiness.canValidateCodes)")
}If you want to refresh them manually, for example after login or app foreground:
Task {
do {
let sdkConfig = try await GrowthCat.shared.refreshSDKConfig()
print("Latest SDK config: \(sdkConfig)")
} catch {
print("Failed to refresh SDK config: \(error)")
}
}Use these as optional feature flags
- `showCodeRedeemUI`: whether you want to present any redeem-code entry points.
- `allowCodeRedeemExecution`: whether you want to allow the user to actually start redemption.
- `sdkReadiness`: backend readiness status from the bootstrap call, useful for debug diagnostics.
8. Integration options
Recommended default
Start with the built-in view if you want RevenueCat-style integration semantics and the safest success/error callback model.
Option A: Built-in view + modifiers
Use GrowthCatReferralView and attach callback modifiers for success and pending/error handling.
import SwiftUI
import GrowthCat
struct SettingsView: View {
@State private var showReferral = false
var body: some View {
Button("Redeem Influencer Code") {
showReferral = true
}
.sheet(isPresented: $showReferral) {
GrowthCatReferralView(
theme: .default,
strings: .default,
verificationEntitlementID: "pro",
verificationTimeout: 20
)
.growthCatOnSuccess { result in
print("Access confirmed for code: \(result.normalizedCode)")
// Safe point to unlock premium features.
}
.growthCatOnError { error in
print("Redemption not confirmed: \(error)")
// Keep user in pending state and reconcile later.
}
}
}
}- User enters a code and taps apply.
- The SDK validates first, then copies the code and opens Apple's sheet.
- The SDK shows a confirming-access loader while entitlement verification runs.
- Invalid codes show a clear inline error and the Apple sheet does not open.
Callback guarantees
- `growthCatOnSuccess`: code validated, Apple sheet shown, app returned to active state, and `verificationEntitlementID` is active in RevenueCat.
- `growthCatOnError`: entitlement was not confirmed in this attempt. Common causes are user cancel, sync delay, network issues, or entitlement not active yet.
Treat `.growthCatOnSuccess` as the confirmed unlock point. Treat `.growthCatOnError` as pending and reconcile later on app active or next launch.
GrowthCatReferralView(
verificationEntitlementID: "pro",
verificationTimeout: 20
)
.growthCatOnSuccess { _ in
Task {
await api.markReferralRedeemed()
// unlockFeaturesForCurrentUser()
}
}
.growthCatOnError { error in
Task {
await api.markReferralPendingVerification(reason: error.localizedDescription)
}
}Option B: Fully custom UI with `GrowthCatRedeemer`
Use your own views and bind to `GrowthCatRedeemer` when you want UI control without dropping to raw APIs.
import SwiftUI
import GrowthCat
struct CustomReferralView: View {
@State private var code = ""
@StateObject private var redeemer = GrowthCatRedeemer()
var body: some View {
VStack(spacing: 12) {
TextField("Your code", text: $code)
.textFieldStyle(.roundedBorder)
Button(redeemer.isLoading ? "Applying..." : "Apply") {
Task { await redeemer.redeem(code) }
}
.disabled(redeemer.isLoading || code.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
if let error = redeemer.lastError {
Text("Error: \(error.localizedDescription)")
.font(.footnote)
}
if let result = redeemer.lastResult {
Text("Applied: \(result.normalizedCode)")
.font(.footnote)
}
}
.padding()
}
}A successful `redeem(...)` result here means the code validated and Apple's sheet was presented. It does not include the built-in view's post-Apple entitlement confirmation callbacks. Verify entitlement after the app returns before unlocking access.
Option C: Raw headless API
let result = try await GrowthCat.shared.redeemReferralCode("ABC123")
let isVerified = await GrowthCat.shared.verifyAppleRedemption(
entitlementID: "pro",
timeout: 20
)Use this path if you want full control over state handling, UI, and lifecycle coordination.
Verification recovery and retry
If the Apple sheet was shown but entitlement confirmation did not land yet, keep the user pending and retry on app active or next launch.
@MainActor
func reconcileReferralStatus() async {
let confirmed = await GrowthCat.shared.verifyAppleRedemption(
entitlementID: "pro",
timeout: 8
)
if confirmed {
await api.markReferralRedeemed()
// unlockFeaturesForCurrentUser()
} else {
await api.markReferralPendingVerification(reason: "Entitlement not active yet")
}
}@MainActor
func refreshReferralRedemptionStatus() async {
let status = await GrowthCat.shared.verifyAppleRedemption(
entitlementID: "pro",
timeout: 8
)
if status {
await api.markReferralRedeemed()
}
}9. Analytics events
Track funnel events tied to a referral code using the same session context across all calls. These are optional — use them if you want granular attribution data (click → validation → paywall → purchase).
let sessionID = GrowthCat.makeSessionID()
let context = GrowthCatAnalyticsContext(sessionID: sessionID, source: "creator_profile")
// 1. User saw the code link
try await GrowthCat.shared.recordReferralClick("MARCO20", context: context)
// 2. User typed and validated the code
let result = try await GrowthCat.shared.validateReferralCode("MARCO20", context: context)
// 3. User reached the paywall
try await GrowthCat.shared.recordAnalyticsEvent(
.paywallViewed,
code: result.normalizedCode,
context: context,
properties: ["paywall_variant": .string("annual_default")]
)Available event names
paywallViewedcheckoutStartedcheckoutCancelledpurchaseSDKCompleted
The SDK auto-fills locale, country_code, platform, app_version, os_version, device_family, and a persistent sdk_install_id when you don't provide them.
10. Built-in view customization
If you pass no theme, GrowthCat uses an adaptive default style for light and dark mode. Customize colors, font, and shape with `GrowthCatTheme`, and customize copy with `GrowthCatStrings`.
let theme = GrowthCatTheme(
primaryColor: .orange,
backgroundColor: Color(.systemGroupedBackground),
textColor: .primary,
fontName: "AvenirNext-DemiBold",
cornerRadius: 14
)let strings = GrowthCatStrings(
title: "Have a Promo Code?",
description: "Enter your influencer code to unlock your offer.",
howItWorksTitle: "How it works",
howItWorksStepValidate: "1. We validate your code instantly.",
howItWorksStepCopy: "2. If valid, we copy it for you.",
howItWorksStepApple: "3. Apple redemption opens so you can paste and continue.",
placeholder: "Enter code",
buttonText: "Apply Discount",
successMessage: "Code validated. Opening Apple redemption...",
invalidCodeMessage: "That code does not exist. Please try again.",
networkErrorMessage: "Network error. Please try again."
)GrowthCatReferralView(theme: theme, strings: strings, verificationEntitlementID: "pro")12. Localization guide
GrowthCat does not impose a localization system. Pass already-localized text into `GrowthCatStrings`, typically using `String(localized:)` and your app's String Catalog keys.
let growthCatStrings = GrowthCatStrings(
title: String(localized: "growthcat.title"),
description: String(localized: "growthcat.description"),
howItWorksTitle: String(localized: "growthcat.how_it_works_title"),
howItWorksStepValidate: String(localized: "growthcat.how_it_works_validate"),
howItWorksStepCopy: String(localized: "growthcat.how_it_works_copy"),
howItWorksStepApple: String(localized: "growthcat.how_it_works_apple"),
placeholder: String(localized: "growthcat.placeholder"),
buttonText: String(localized: "growthcat.button_text"),
successMessage: String(localized: "growthcat.success_message"),
invalidCodeMessage: String(localized: "growthcat.invalid_code"),
networkErrorMessage: String(localized: "growthcat.network_error")
)
GrowthCatReferralView(strings: growthCatStrings, verificationEntitlementID: "pro")Recommended localization keys
- `growthcat.title`
- `growthcat.description`
- `growthcat.how_it_works_title`
- `growthcat.how_it_works_validate`
- `growthcat.how_it_works_copy`
- `growthcat.how_it_works_apple`
- `growthcat.placeholder`
- `growthcat.button_text`
- `growthcat.success_message`
- `growthcat.invalid_code`
- `growthcat.network_error`
- `growthcat.banner.title`
- `growthcat.banner.subtitle`
- `growthcat.banner.cta`
- Add the new language in your String Catalog or localized strings files.
- Translate your `growthcat.*` keys.
- Build `GrowthCatStrings` from localized keys.
- Pass those strings into `GrowthCatReferralView`.
13. Error handling
`redeemReferralCode(_:)` throws `GrowthCatError`.
public enum GrowthCatError: Error {
case notInitialized
case invalidCode(message: String?)
case appSetupIncomplete(message: String?, requirements: AppSetupRequirements)
case unauthorized(message: String?)
case rateLimited(retryAfter: TimeInterval?)
case network(message: String?)
case server(statusCode: Int, message: String?)
case revenueCatUnavailable(message: String?)
case unknown(message: String?)
}Best practices
- Initialize GrowthCat once at startup.
- Configure RevenueCat before redemption calls.
- Keep RevenueCat identity stable before starting the referral flow.
- Pass `verificationEntitlementID` to verify redemption outcome.
- Use `.growthCatOnSuccess` as confirmed redemption.
- Keep referral entry points visible in durable parts of the app.
14. API reference
// ─── Initialization ──────────────────────────────────────────────────────────
public enum GrowthCat {
public static func initialize(
apiKey: String,
environmentMode: GrowthCatEnvironmentMode = .automatic,
logsEnabled: Bool = false,
baseURL: URL? = nil,
measurementMode: GrowthCatMeasurementMode
)
public static var shared: GrowthCatClient { get }
public static func makeSessionID() -> String
public static func setMeasurementMode(_ mode: GrowthCatMeasurementMode)
}
// ─── GrowthCatClient ─────────────────────────────────────────────────────────
public struct GrowthCatClient {
// Config
public var sdkConfig: GrowthCatSDKConfig? { get }
public var sdkBootstrap: GrowthCatSDKBootstrap? { get }
public var sdkReadiness: GrowthCatSDKReadiness? { get }
public func refreshSDKConfig() async throws -> GrowthCatSDKConfig
// Code redemption
public func redeemReferralCode(_ code: String) async throws -> ReferralRedemptionResult
public func validateReferralCode(_ code: String) async throws -> ReferralRedemptionResult
public func openAppleRedemptionSheet() async throws
public func verifyAppleRedemption(entitlementID: String, timeout: TimeInterval) async -> Bool
// Analytics
public func recordReferralClick(_ code: String, context: GrowthCatAnalyticsContext) async throws
public func recordAnalyticsEvent(
_ eventName: GrowthCatAnalyticsEventName,
code: String?,
eventAt: Date?,
context: GrowthCatAnalyticsContext,
properties: [String: GrowthCatAnalyticsValue]?
) async throws
}
// ─── GrowthCatRedeemer ───────────────────────────────────────────────────────
@MainActor
public final class GrowthCatRedeemer: ObservableObject {
@Published public private(set) var isLoading: Bool
@Published public private(set) var lastResult: ReferralRedemptionResult?
@Published public private(set) var lastError: GrowthCatError?
public func redeem(_ code: String) async
}