### Setup Development Environment Source: https://github.com/superwall/superwall-ios/blob/develop/CLAUDE.md Run this script to initialize the development environment, including installing SwiftLint and setting up git hooks. Ensure xcodegen is installed via Homebrew. ```bash scripts/setup.sh brew install xcodegen ``` -------------------------------- ### Install SuperwallKit via Cocoapods Source: https://github.com/superwall/superwall-ios/blob/develop/README.md Add SuperwallKit to your Podfile for installation using Cocoapods. Ensure your local spec repo is updated before installing. ```ruby pod 'SuperwallKit', '< 5.0.0' ``` -------------------------------- ### Preemptively Get Paywall Presentation Result Source: https://context7.com/superwall/superwall-ios/llms.txt Checks what would happen if a given placement were registered, without showing a paywall. Useful for adapting UI before triggering a placement. ```swift Task { let result = await Superwall.shared.getPresentationResult( forPlacement: "premium_export", params: ["document_type": "pdf"] ) switch result { case .paywall(let experiment): // A paywall would show – e.g. dim a button to hint gating print("Would show paywall for experiment:", experiment.id) case .holdout(let experiment): print("User is in holdout:", experiment.id) case .noRuleMatch: print("User does not match any audience") case .placementNotFound: print("Placement not found in any campaign") case .userIsSubscribed: print("User already subscribed – no paywall needed") case .paywallNotAvailable: print("Paywall configured but not available") } } ``` -------------------------------- ### Get Paywall for Manual Presentation Source: https://context7.com/superwall/superwall-ios/llms.txt Retrieves a `PaywallViewController` for manual presentation, bypassing Superwall's automatic presentation logic. You control how and when it appears. ```APIDOC ## `Superwall.shared.getPaywall(forPlacement:delegate:)` Retrieves a `PaywallViewController` for manual presentation, bypassing Superwall's automatic presentation logic. You control how and when it appears. ```swift class FeatureViewController: UIViewController, PaywallViewControllerDelegate { func presentCustomPaywall() { Task { let paywallVC = try await Superwall.shared.getPaywall( forPlacement: "custom_paywall_placement", params: ["source": "feature_button"], delegate: self ) paywallVC.modalPresentationStyle = .pageSheet present(paywallVC, animated: true) } catch let reason as PaywallSkippedReason { print("Paywall skipped:", reason) } catch { print("Error fetching paywall:", error.localizedDescription) } } // PaywallViewControllerDelegate func paywall( _ paywall: PaywallViewController, didFinishWith result: PaywallResult, shouldDismiss: Bool ) { if shouldDismiss { dismiss(animated: true) } switch result { case .purchased(let product): print("Purchased:", product.productIdentifier) case .restored: print("Restored") case .declined: print("Declined") } } } ``` ``` -------------------------------- ### Manually Get and Present Paywall Source: https://context7.com/superwall/superwall-ios/llms.txt Retrieves a `PaywallViewController` for manual presentation, bypassing Superwall's automatic logic. You control how and when it appears. Implement `PaywallViewControllerDelegate` to handle paywall events. ```swift class FeatureViewController: UIViewController, PaywallViewControllerDelegate { func presentCustomPaywall() { Task { @MainActor in do { let paywallVC = try await Superwall.shared.getPaywall( forPlacement: "custom_paywall_placement", params: ["source": "feature_button"], delegate: self ) paywallVC.modalPresentationStyle = .pageSheet present(paywallVC, animated: true) } catch let reason as PaywallSkippedReason { print("Paywall skipped:", reason) } catch { print("Error fetching paywall:", error.localizedDescription) } } } // PaywallViewControllerDelegate func paywall( _ paywall: PaywallViewController, didFinishWith result: PaywallResult, shouldDismiss: Bool ) { if shouldDismiss { dismiss(animated: true) } switch result { case .purchased(let product): print("Purchased:", product.productIdentifier) case .restored: print("Restored") case .declined: print("Declined") } } } ``` -------------------------------- ### Setting User Attributes Source: https://context7.com/superwall/superwall-ios/llms.txt Stores arbitrary user attributes on Superwall's servers for use in audience filters and paywall personalization. Keys starting with `$` are reserved. Arrays/dictionaries as values are dropped. ```APIDOC ## `Superwall.shared.setUserAttributes(_:)` Stores arbitrary user attributes on Superwall's servers for use in audience filters and paywall personalization. Keys starting with `$` are reserved. Arrays/dictionaries as values are dropped. ```swift // Set attributes after loading user profile Superwall.shared.setUserAttributes([ "name": "Jane Smith", "email": "jane@example.com", "plan_tier": "free", "signup_date": Date(), "profile_url": URL(string: "https://example.com/jane"), "total_sessions": 42, "is_beta_tester": true ]) // Remove an attribute by setting its value to nil Superwall.shared.setUserAttributes(["plan_tier": nil]) // Read back current attributes let attrs = Superwall.shared.userAttributes print("Stored attributes:", attrs) ``` ``` -------------------------------- ### Configure SuperwallOptions and PaywallOptions Source: https://context7.com/superwall/superwall-ios/llms.txt Customize SDK behavior and paywall appearance using `SuperwallOptions` and `PaywallOptions`. These are passed to `Superwall.configure`. ```swift let options = SuperwallOptions() // --- Paywall appearance & behaviour --- options.paywalls.isHapticFeedbackEnabled = true options.paywalls.automaticallyDismiss = true options.paywalls.shouldPreload = true options.paywalls.shouldShowPurchaseFailureAlert = true options.paywalls.shouldShowWebRestorationAlert = true options.paywalls.transactionBackgroundView = .spinner options.paywalls.restoreFailed.title = "Nothing Found" options.paywalls.restoreFailed.message = "We couldn't find an active subscription." options.paywalls.restoreFailed.closeButtonTitle = "OK" // Override specific products on all paywalls by name options.paywalls.overrideProductsByName = [ "primary": "com.app.premium.monthly", "secondary": "com.app.premium.annual" ] // --- Serve paywall assets from bundle (skip network download) --- options.localResources = [ "hero_image": Bundle.main.url(forResource: "hero", withExtension: "png")!, "app_logo": UIImage(named: "Logo")! ] // --- General SDK settings --- options.storeKitVersion = .storeKit2 // auto-selected; override if needed options.shouldObservePurchases = false // set true to watch StoreKit outside Superwall options.isExternalDataCollectionEnabled = true options.localeIdentifier = "en_US" // override for testing locale-specific paywalls options.maxConfigRetryCount = 6 options.logging.level = .warn options.logging.scopes = [.paywallPresentation, .network] Superwall.configure( apiKey: "pk_your_api_key", options: options ) ``` -------------------------------- ### Initialize Superwall SDK Source: https://context7.com/superwall/superwall-ios/llms.txt Initializes the shared Superwall instance. Call this once in your application's entry point. You can provide a custom purchase controller and options for advanced configuration. ```swift import SuperwallKit // Minimal – Superwall manages all purchases automatically Superwall.configure(apiKey: "pk_your_api_key") // Full configuration with a custom PurchaseController and options let options = SuperwallOptions() options.paywalls.shouldPreload = true options.paywalls.automaticallyDismiss = true options.logging.level = .info options.storeKitVersion = .storeKit2 Superwall.configure( apiKey: "pk_your_api_key", purchaseController: MyPurchaseController(), options: options ) { print("Superwall configured – status:", Superwall.shared.configurationStatus) // Expected: Superwall configured – status: configured } ``` -------------------------------- ### Configuring the SDK Source: https://github.com/superwall/superwall-ios/blob/develop/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md Methods for configuring the Superwall SDK with your API key and purchase controller. ```APIDOC ## `configure(apiKey:purchaseController:options:completion:)-52tke` ### Description Configures the Superwall SDK with the provided API key, purchase controller, and options. A completion handler is called when configuration is complete. ### Method `configure` ### Parameters - **apiKey** (String) - Your Superwall API key. - **purchaseController** (PurchaseController) - An object conforming to the `PurchaseController` protocol. - **options** (SuperwallOptions?) - Optional configuration options for the SDK. - **completion** ((() -> Void)?) - An optional completion handler called when configuration is finished. ``` ```APIDOC ## `configure(apiKey:purchaseController:options:completion:)-ds2x` ### Description Configures the Superwall SDK with the provided API key, purchase controller, and options. A completion handler is called when configuration is complete. ### Method `configure` ### Parameters - **apiKey** (String) - Your Superwall API key. - **purchaseController** (PurchaseControllerObjc) - An object conforming to the `PurchaseControllerObjc` protocol. - **options** (SuperwallOptions?) - Optional configuration options for the SDK. - **completion** ((() -> Void)?) - An optional completion handler called when configuration is finished. ``` ```APIDOC ## `configure(apiKey:)` ### Description Configures the Superwall SDK with the provided API key. This is a simplified configuration. ### Method `configure` ### Parameters - **apiKey** (String) - Your Superwall API key. ``` -------------------------------- ### Superwall.configure Source: https://context7.com/superwall/superwall-ios/llms.txt Initializes the shared Superwall instance. Call this once during app startup. It can be configured with an API key, an optional purchase controller, and options for customization. A completion block is provided to execute code once the SDK finishes fetching remote configuration. ```APIDOC ## `Superwall.configure(apiKey:purchaseController:options:completion:)` ### Description Initializes the shared `Superwall` instance. Call this once in `application(_:didFinishLaunchingWithOptions:)` or the SwiftUI `App` initializer. Returns the configured instance and fires the optional `completion` block when the SDK finishes fetching its remote configuration. ### Parameters - **apiKey** (string) - Required - Your Superwall API key. - **purchaseController** (PurchaseController) - Optional - A custom purchase controller to manage subscription logic. - **options** (SuperwallOptions) - Optional - Configuration options for the SDK. - **completion** ( () -> Void ) - Optional - A closure to be executed when the SDK finishes fetching remote configuration. ### Request Example ```swift import SuperwallKit // Minimal configuration Superwall.configure(apiKey: "pk_your_api_key") // Full configuration let options = SuperwallOptions() options.paywalls.shouldPreload = true options.paywalls.automaticallyDismiss = true options.logging.level = .info options.storeKitVersion = .storeKit2 Superwall.configure( apiKey: "pk_your_api_key", purchaseController: MyPurchaseController(), options: options ) { print("Superwall configured – status:", Superwall.shared.configurationStatus) } ``` ``` -------------------------------- ### Build Framework via Command Line Source: https://github.com/superwall/superwall-ios/blob/develop/CLAUDE.md Use this script to build the SuperwallKit framework using xcodebuild. It automatically runs xcodegen to ensure the Xcode project is up-to-date. ```bash scripts/build.sh ``` -------------------------------- ### SuperwallOptions and PaywallOptions Configuration Source: https://context7.com/superwall/superwall-ios/llms.txt Configure the Superwall SDK's behavior and appearance using `SuperwallOptions` and `PaywallOptions`. This allows for fine-grained control over paywall settings, local resource loading, and general SDK behavior. ```APIDOC ## `SuperwallOptions` and `PaywallOptions` Configuration Fine-grained control over SDK behaviour, passed to `Superwall.configure`. ```swift let options = SuperwallOptions() // --- Paywall appearance & behaviour --- options.paywalls.isHapticFeedbackEnabled = true options.paywalls.automaticallyDismiss = true options.paywalls.shouldPreload = true options.paywalls.shouldShowPurchaseFailureAlert = true options.paywalls.shouldShowWebRestorationAlert = true options.paywalls.transactionBackgroundView = .spinner options.paywalls.restoreFailed.title = "Nothing Found" options.paywalls.restoreFailed.message = "We couldn't find an active subscription." options.paywalls.restoreFailed.closeButtonTitle = "OK" // Override specific products on all paywalls by name options.paywalls.overrideProductsByName = [ "primary": "com.app.premium.monthly", "secondary": "com.app.premium.annual" ] // --- Serve paywall assets from bundle (skip network download) --- options.localResources = [ "hero_image": Bundle.main.url(forResource: "hero", withExtension: "png")!, "app_logo": UIImage(named: "Logo")! ] // --- General SDK settings --- options.storeKitVersion = .storeKit2 // auto-selected; override if needed options.shouldObservePurchases = false // set true to watch StoreKit outside Superwall options.isExternalDataCollectionEnabled = true options.localeIdentifier = "en_US" // override for testing locale-specific paywalls options.maxConfigRetryCount = 6 options.logging.level = .warn options.logging.scopes = [.paywallPresentation, .network] Superwall.configure( apiKey: "pk_your_api_key", options: options ) ``` ``` -------------------------------- ### Set and Remove User Attributes Source: https://context7.com/superwall/superwall-ios/llms.txt Stores arbitrary user attributes for use in audience filters and paywall personalization. Keys starting with `$` are reserved. Arrays/dictionaries as values are dropped. Attributes can be removed by setting their value to nil. ```swift // Set attributes after loading user profile Superwall.shared.setUserAttributes([ "name": "Jane Smith", "email": "jane@example.com", "plan_tier": "free", "signup_date": Date(), "profile_url": URL(string: "https://example.com/jane"), "total_sessions": 42, "is_beta_tester": true ]) // Remove an attribute by setting its value to nil Superwall.shared.setUserAttributes(["plan_tier": nil]) // Read back current attributes let attrs = Superwall.shared.userAttributes print("Stored attributes:", attrs) ``` -------------------------------- ### Handling Purchases Source: https://github.com/superwall/superwall-ios/blob/develop/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md Methods for initiating purchases and restoring previous purchases. ```APIDOC ## `purchase(_:)` ### Description Initiates a purchase for a given product. ### Method `purchase` ### Parameters - **product** (Product) - The product to purchase. ``` ```APIDOC ## `purchase(_:completion:)-6oyxm` ### Description Initiates a purchase for a given product with a completion handler. ### Method `purchase` ### Parameters - **product** (Product) - The product to purchase. - **completion** (PurchaseCompletion) - The completion handler that receives the purchase result. ``` ```APIDOC ## `purchase(_:completion:)-4rj6r` ### Description Initiates a purchase for a given product with a completion handler. ### Method `purchase` ### Parameters - **product** (Product) - The product to purchase. - **completion** (PurchaseCompletionObjc) - The completion handler that receives the purchase result. ``` ```APIDOC ## `restorePurchases()` ### Description Restores previous purchases made by the user. ### Method `restorePurchases` ``` ```APIDOC ## `restorePurchases(completion:)-4fx45` ### Description Restores previous purchases with a completion handler. ### Method `restorePurchases` ### Parameters - **completion** (RestorePurchasesCompletion) - The completion handler that receives the restore result. ``` ```APIDOC ## `restorePurchases(completion:)-4cxt5` ### Description Restores previous purchases with a completion handler. ### Method `restorePurchases` ### Parameters - **completion** (RestorePurchasesCompletionObjc) - The completion handler that receives the restore result. ``` -------------------------------- ### Implement PurchaseController for Custom Purchase Logic Source: https://context7.com/superwall/superwall-ios/llms.txt Integrate your own StoreKit or RevenueCat purchase logic by implementing the PurchaseController protocol. Ensure Superwall.shared.subscriptionStatus is kept in sync. ```swift import SuperwallKit import StoreKit final class SWPurchaseController: PurchaseController { // Called by Superwall when the user taps "Buy" on a paywall @MainActor func purchase(product: StoreProduct) async -> PurchaseResult { guard let sk2Product = product.sk2Product else { return .failed(PurchaseError.missingProduct) } do { let result = try await sk2Product.purchase() switch result { case .success(let verification): let transaction = try verification.payloadValue await transaction.finish() return .purchased case .userCancelled: return .cancelled case .pending: return .pending @unknown default: return .failed(PurchaseError.unknown) } } catch { return .failed(error) } } // Called by Superwall when the user taps "Restore" on a paywall @MainActor func restorePurchases() async -> RestorationResult { do { try await AppStore.sync() return .restored } catch { return .failed(error) } } // Call this on launch to hydrate Superwall's subscription status func syncSubscriptionStatus() async { for await result in StoreKit.Transaction.currentEntitlements { guard case .verified(let transaction) = result else { continue } // Map your product IDs to Superwall entitlement IDs let entitlements: Set = [Entitlement(id: "premium")] await MainActor.run { Superwall.shared.subscriptionStatus = .active(entitlements) } return } await MainActor.run { Superwall.shared.subscriptionStatus = .inactive } } } // Usage in App init: let purchaseController = SWPurchaseController() Superwall.configure(apiKey: "pk_your_api_key", purchaseController: purchaseController) Task { await purchaseController.syncSubscriptionStatus() } ``` -------------------------------- ### Helpers Source: https://github.com/superwall/superwall-ios/blob/develop/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md Utility methods for interacting with the SDK. ```APIDOC ## `togglePaywallSpinner(isHidden:)` ### Description Toggles the visibility of the paywall loading spinner. ### Method `togglePaywallSpinner` ### Parameters - **isHidden** (Bool) - Set to `true` to hide the spinner, `false` to show it. ``` ```APIDOC ## `latestPaywallInfo` ### Description Provides information about the most recently presented paywall. ### Property `latestPaywallInfo` (PaywallInfo?) ``` ```APIDOC ## `presentedViewController` ### Description Returns the view controller of the currently presented paywall, if any. ### Property `presentedViewController` (UIViewController?) ``` ```APIDOC ## `userId` ### Description Returns the identifier of the currently logged-in user. ### Property `userId` (String?) ``` ```APIDOC ## `isLoggedIn` ### Description Returns `true` if a user is currently logged in, `false` otherwise. ### Property `isLoggedIn` (Bool) ``` -------------------------------- ### Superwall.configure — PurchaseController Protocol Source: https://context7.com/superwall/superwall-ios/llms.txt Implement `PurchaseController` to integrate your own StoreKit or RevenueCat purchase logic. You must also keep `Superwall.shared.subscriptionStatus` in sync. ```APIDOC ## `Superwall.configure` — `PurchaseController` Protocol Implement `PurchaseController` to integrate your own StoreKit or RevenueCat purchase logic. You must also keep `Superwall.shared.subscriptionStatus` in sync. ```swift import SuperwallKit import StoreKit final class SWPurchaseController: PurchaseController { // Called by Superwall when the user taps "Buy" on a paywall @MainActor func purchase(product: StoreProduct) async -> PurchaseResult { guard let sk2Product = product.sk2Product else { return .failed(PurchaseError.missingProduct) } do { let result = try await sk2Product.purchase() switch result { case .success(let verification): let transaction = try verification.payloadValue await transaction.finish() return .purchased case .userCancelled: return .cancelled case .pending: return .pending @unknown default: return .failed(PurchaseError.unknown) } } catch { return .failed(error) } } // Called by Superwall when the user taps "Restore" on a paywall @MainActor func restorePurchases() async -> RestorationResult { do { try await AppStore.sync() return .restored } catch { return .failed(error) } } // Call this on launch to hydrate Superwall's subscription status func syncSubscriptionStatus() async { for await result in StoreKit.Transaction.currentEntitlements { guard case .verified(let transaction) = result else { continue } // Map your product IDs to Superwall entitlement IDs let entitlements: Set = [Entitlement(id: "premium")] await MainActor.run { Superwall.shared.subscriptionStatus = .active(entitlements) } return } await MainActor.run { Superwall.shared.subscriptionStatus = .inactive } } } // Usage in App init: let purchaseController = SWPurchaseController() Superwall.configure(apiKey: "pk_your_api_key", purchaseController: purchaseController) Task { await purchaseController.syncSubscriptionStatus() } ``` ``` -------------------------------- ### Lint Code with SwiftLint Source: https://github.com/superwall/superwall-ios/blob/develop/CLAUDE.md Run this script to apply SwiftLint for code style checks based on the .swiftlint.yml configuration. It helps maintain consistent code formatting. ```bash scripts/lint.sh ``` -------------------------------- ### Presenting and Dismissing a Paywall Source: https://github.com/superwall/superwall-ios/blob/develop/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md Methods for registering events, retrieving paywalls, and presenting or dismissing them. ```APIDOC ## `register(placement:params:handler:feature:)` ### Description Registers an event with optional parameters and a handler for when the paywall is presented or dismissed. Also allows specifying a feature to track. ### Method `register` ### Parameters - **placement** (String) - The name of the placement to register. - **params** (Dictionary) - Optional parameters to associate with the event. - **handler** (PaywallPresentationHandler?) - A handler for paywall presentation events. - **feature** (String?) - An optional feature name to associate with the registration. ``` ```APIDOC ## `register(placement:params:handler:)` ### Description Registers an event with optional parameters and a handler for when the paywall is presented or dismissed. ### Method `register` ### Parameters - **placement** (String) - The name of the placement to register. - **params** (Dictionary) - Optional parameters to associate with the event. - **handler** (PaywallPresentationHandler?) - A handler for paywall presentation events. ``` ```APIDOC ## `register(placement:)` ### Description Registers an event for a given placement. ### Method `register` ### Parameters - **placement** (String) - The name of the placement to register. ``` ```APIDOC ## `register(placement:params:)` ### Description Registers an event with optional parameters for a given placement. ### Method `register` ### Parameters - **placement** (String) - The name of the placement to register. - **params** (Dictionary) - Optional parameters to associate with the event. ``` ```APIDOC ## `getPaywall(forPlacement:params:paywallOverrides:delegate:)` ### Description Retrieves a paywall for a given placement with optional parameters, paywall overrides, and a delegate. ### Method `getPaywall` ### Parameters - **placement** (String) - The name of the placement. - **params** (Dictionary?) - Optional parameters. - **paywallOverrides** (PaywallOverrides?) - Optional paywall overrides. - **delegate** (PaywallViewControllerDelegate?) - The delegate for the paywall view controller. ``` ```APIDOC ## `getPaywall(forPlacement:params:paywallOverrides:delegate:completion:)-8u1n` ### Description Retrieves a paywall for a given placement with optional parameters, paywall overrides, and a delegate, using a completion handler. ### Method `getPaywall` ### Parameters - **placement** (String) - The name of the placement. - **params** (Dictionary?) - Optional parameters. - **paywallOverrides** (PaywallOverrides?) - Optional paywall overrides. - **delegate** (PaywallViewControllerDelegate?) - The delegate for the paywall view controller. - **completion** (GetPaywallResultObjc) - The completion handler that receives the result. ``` ```APIDOC ## `getPaywall(forPlacement:params:paywallOverrides:delegate:completion:)-5vtpb` ### Description Retrieves a paywall for a given placement with optional parameters, paywall overrides, and a delegate, using a completion handler. ### Method `getPaywall` ### Parameters - **placement** (String) - The name of the placement. - **params** (Dictionary?) - Optional parameters. - **paywallOverrides** (PaywallOverrides?) - Optional paywall overrides. - **delegate** (PaywallViewControllerDelegate?) - The delegate for the paywall view controller. - **completion** (GetPaywallResult) - The completion handler that receives the result. ``` ```APIDOC ## `getPresentationResult(forPlacement:)` ### Description Gets the presentation result for a given placement. ### Method `getPresentationResult` ### Parameters - **placement** (String) - The name of the placement. ``` ```APIDOC ## `getPresentationResult(forPlacement:params:)-9ivi6` ### Description Gets the presentation result for a given placement with optional parameters. ### Method `getPresentationResult` ### Parameters - **placement** (String) - The name of the placement. - **params** (Dictionary?) - Optional parameters. ``` ```APIDOC ## `getPresentationResult(forPlacement:params:)-60qtr` ### Description Gets the presentation result for a given placement with optional parameters. ### Method `getPresentationResult` ### Parameters - **placement** (String) - The name of the placement. - **params** (Dictionary?) - Optional parameters. ``` ```APIDOC ## `getPresentationResult(forPlacement:params:completion:)` ### Description Gets the presentation result for a given placement with optional parameters, using a completion handler. ### Method `getPresentationResult` ### Parameters - **placement** (String) - The name of the placement. - **params** (Dictionary?) - Optional parameters. - **completion** (GetPresentationResultCompletion) - The completion handler that receives the result. ``` ```APIDOC ## `dismiss()-844a9` ### Description Dismisses the currently presented paywall. ### Method `dismiss` ``` ```APIDOC ## `dismiss()-4objm` ### Description Dismisses the currently presented paywall. ### Method `dismiss` ``` ```APIDOC ## `dismiss(completion:)` ### Description Dismisses the currently presented paywall with a completion handler. ### Method `dismiss` ### Parameters - **completion** ((() -> Void)?) - An optional completion handler called after dismissal. ``` -------------------------------- ### In-App Previews Source: https://github.com/superwall/superwall-ios/blob/develop/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md Handles deep links for in-app previews. ```APIDOC ## `handleDeepLink(_:)` ### Description Handles a deep link, which can be used for in-app previews. ### Method `handleDeepLink` ### Parameters - **url** (URL) - The deep link URL to handle. ``` -------------------------------- ### RevenueCat Integration via PurchaseController Source: https://context7.com/superwall/superwall-ios/llms.txt Implements a `PurchaseController` to integrate Superwall with RevenueCat, merging entitlements and handling purchases and restorations. ```APIDOC ## RevenueCat Integration via `PurchaseController` Full RevenueCat integration pattern that merges RC entitlements with Superwall web entitlements. ```swift import SuperwallKit import RevenueCat final class RCPurchaseController: PurchaseController { func syncSubscriptionStatus() { // Listen to RevenueCat customer info stream Task { for await customerInfo in Purchases.shared.customerInfoStream { let rcEntitlements = Set( customerInfo.entitlements.activeInCurrentEnvironment.keys .map { Entitlement(id: $0) } ) await updateStatus(with: rcEntitlements) } } // Also re-sync when Superwall's customerInfo changes (web entitlements) Task { for await _ in Superwall.shared.customerInfoStream { guard let info = try? await Purchases.shared.customerInfo() else { continue } let rcEntitlements = Set( info.entitlements.activeInCurrentEnvironment.keys .map { Entitlement(id: $0) } ) await updateStatus(with: rcEntitlements) } } } @MainActor private func updateStatus(with rcEntitlements: Set) async { let webEntitlements = Superwall.shared.entitlements.web let all = rcEntitlements.union(webEntitlements) Superwall.shared.subscriptionStatus = all.isEmpty ? .inactive : .active(all) } @MainActor func purchase(product: StoreProduct) async -> PurchaseResult { guard let sk2Product = product.sk2Product else { return .failed(nil) } do { let result = try await Purchases.shared.purchase( product: RevenueCat.StoreProduct(sk2Product: sk2Product) ) return result.userCancelled ? .cancelled : .purchased } catch let e as ErrorCode where e == .paymentPendingError { return .pending } catch { return .failed(error) } } @MainActor func restorePurchases() async -> RestorationResult { do { _ = try await Purchases.shared.restorePurchases() return .restored } catch { return .failed(error) } } } // App init let rc = RCPurchaseController() Superwall.configure(apiKey: "pk_superwall_key", purchaseController: rc) Purchases.configure(withAPIKey: "rc_api_key") rc.syncSubscriptionStatus() ``` ``` -------------------------------- ### Run Tests via Command Line Source: https://github.com/superwall/superwall-ios/blob/develop/CLAUDE.md Execute this script to run tests using xcodebuild. It also automatically runs xcodegen. ```bash scripts/test.sh ``` -------------------------------- ### RevenueCat Integration with Superwall Source: https://context7.com/superwall/superwall-ios/llms.txt Implement a `PurchaseController` to merge RevenueCat entitlements with Superwall web entitlements for a unified subscription status. Listen to both RevenueCat and Superwall customer info streams. ```swift import SuperwallKit import RevenueCat final class RCPurchaseController: PurchaseController { func syncSubscriptionStatus() { // Listen to RevenueCat customer info stream Task { for await customerInfo in Purchases.shared.customerInfoStream { let rcEntitlements = Set( customerInfo.entitlements.activeInCurrentEnvironment.keys .map { Entitlement(id: $0) } ) await updateStatus(with: rcEntitlements) } } // Also re-sync when Superwall's customerInfo changes (web entitlements) Task { for await _ in Superwall.shared.customerInfoStream { guard let info = try? await Purchases.shared.customerInfo() else { continue } let rcEntitlements = Set( info.entitlements.activeInCurrentEnvironment.keys .map { Entitlement(id: $0) } ) await updateStatus(with: rcEntitlements) } } } @MainActor private func updateStatus(with rcEntitlements: Set) async { let webEntitlements = Superwall.shared.entitlements.web let all = rcEntitlements.union(webEntitlements) Superwall.shared.subscriptionStatus = all.isEmpty ? .inactive : .active(all) } @MainActor func purchase(product: StoreProduct) async -> PurchaseResult { guard let sk2Product = product.sk2Product else { return .failed(nil) } do { let result = try await Purchases.shared.purchase( product: RevenueCat.StoreProduct(sk2Product: sk2Product) ) return result.userCancelled ? .cancelled : .purchased } catch let e as ErrorCode where e == .paymentPendingError { return .pending } catch { return .failed(error) } } @MainActor func restorePurchases() async -> RestorationResult { do { _ = try await Purchases.shared.restorePurchases() return .restored } catch { return .failed(error) } } } // App init let rc = RCPurchaseController() Superwall.configure(apiKey: "pk_superwall_key", purchaseController: rc) Purchases.configure(withAPIKey: "rc_api_key") rc.syncSubscriptionStatus() ``` -------------------------------- ### Preemptively Check Presentation Result Source: https://context7.com/superwall/superwall-ios/llms.txt Preemptively checks what would happen if a given placement were registered, without actually showing a paywall. Useful for adapting UI before triggering a placement. ```APIDOC ## `Superwall.shared.getPresentationResult(forPlacement:params:)` Preemptively checks what would happen if a given placement were registered, without actually showing a paywall. Useful for adapting UI before triggering a placement. ```swift Task { let result = await Superwall.shared.getPresentationResult( forPlacement: "premium_export", params: ["document_type": "pdf"] ) switch result { case .paywall(let experiment): // A paywall would show – e.g. dim a button to hint gating print("Would show paywall for experiment:", experiment.id) case .holdout(let experiment): print("User is in holdout:", experiment.id) case .noRuleMatch: print("User does not match any audience") case .placementNotFound: print("Placement not found in any campaign") case .userIsSubscribed: print("User already subscribed – no paywall needed") case .paywallNotAvailable: print("Paywall configured but not available") } } ``` ``` -------------------------------- ### Preload Paywalls with Superwall Source: https://context7.com/superwall/superwall-ios/llms.txt Manually control paywall preloading by disabling `shouldPreload` and using `preloadAllPaywalls()` or `preloadPaywalls(forPlacements:)`. `refreshConfiguration()` can be used to force a config fetch. ```swift // Disable automatic preloading at configure time let options = SuperwallOptions() options.paywalls.shouldPreload = false Superwall.configure(apiKey: "pk_your_api_key", options: options) // Later, preload everything after the user finishes onboarding func onboardingCompleted() { Superwall.shared.preloadAllPaywalls() } // Or preload only specific placements when approaching a feature gate func userApproachingPremiumFeature() { Superwall.shared.preloadPaywalls(forPlacements: [ "premium_export", "advanced_filters", "collaboration_gate" ]) } // Force a fresh config fetch during development Task { await Superwall.shared.refreshConfiguration() print("Config refreshed") } ``` -------------------------------- ### Paywall Preloading Source: https://context7.com/superwall/superwall-ios/llms.txt Manually control paywall preloading when `shouldPreload` is disabled for fine-grained caching. Includes options for preloading all paywalls or specific placements, and refreshing configuration. ```APIDOC ## `Superwall.shared.preloadAllPaywalls()` and `preloadPaywalls(forPlacements:)` Manually control paywall preloading when `shouldPreload` is disabled for fine-grained caching. ```swift // Disable automatic preloading at configure time let options = SuperwallOptions() options.paywalls.shouldPreload = false Superwall.configure(apiKey: "pk_your_api_key", options: options) // Later, preload everything after the user finishes onboarding func onboardingCompleted() { Superwall.shared.preloadAllPaywalls() } // Or preload only specific placements when approaching a feature gate func userApproachingPremiumFeature() { Superwall.shared.preloadPaywalls(forPlacements: [ "premium_export", "advanced_filters", "collaboration_gate" ]) } // Force a fresh config fetch during development Task { await Superwall.shared.refreshConfiguration() print("Config refreshed") } ``` ``` -------------------------------- ### Superwall.handleDeepLink(_:) Source: https://context7.com/superwall/superwall-ios/llms.txt Handles deep links for paywall previews, web redemption codes, and campaign triggers. Returns true if Superwall handles the URL. ```APIDOC ## `Superwall.handleDeepLink(_:)` (Static) Routes deep links to Superwall for paywall previews, web redemption codes, and `deepLink_open` campaign triggers. Returns `true` when Superwall handles the URL. ```swift // SwiftUI – in the App body WindowGroup { ContentView() .onOpenURL { url in if !Superwall.handleDeepLink(url) { // Handle non-Superwall deep links AppRouter.handle(url) } } } // UIKit – in AppDelegate or SceneDelegate func application( _ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { return Superwall.handleDeepLink(url) } func scene( _ scene: UIScene, continue userActivity: NSUserActivity) { if let url = userActivity.webpageURL { Superwall.handleDeepLink(url) } } ``` ``` -------------------------------- ### Register Placement with Feature Gate Source: https://context7.com/superwall/superwall-ios/llms.txt Registers a named placement to potentially show a paywall. The optional `feature` closure is executed when the user has access (subscribed, or the paywall is non-gated and dismissed). Use this to gate premium features. ```swift // Gate a premium feature behind a placement Superwall.shared.register( placement: "premium_export", params: ["document_type": "pdf", "page_count": 42], handler: { let handler = PaywallPresentationHandler() handler.onPresent { paywallInfo in print("Paywall presented:", paywallInfo.name) } handler.onDismiss { paywallInfo, result in switch result { case .purchased(let product): print("User purchased:", product.productIdentifier) case .restored: print("Purchases restored") case .declined: print("Paywall declined") } } handler.onSkip { reason in print("Paywall skipped:", reason) } handler.onError { error in print("Presentation error:", error.localizedDescription) } return handler }() ) { // This block executes when the user has access exportDocument(as: .pdf) } ``` -------------------------------- ### Implement SuperwallDelegate Protocol Source: https://context7.com/superwall/superwall-ios/llms.txt Conform to the `SuperwallDelegate` protocol to receive lifecycle events, analytics, and custom action callbacks. Assign an instance of your delegate to `Superwall.shared.delegate` after configuring the SDK. ```swift import SuperwallKit class MySuperwallDelegate: SuperwallDelegate { // Track all internal Superwall analytics in your own pipeline func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) { let params = eventInfo.params switch eventInfo.event { case .paywallOpen(let paywallInfo): Analytics.track("paywall_open", properties: [ "paywall_name": paywallInfo.name, "paywall_id": paywallInfo.identifier ]) case .transactionComplete(let transaction, let product, _, _): Analytics.track("purchase", properties: [ "product_id": product.productIdentifier, "transaction_id": transaction?.id ?? "" ]) case .paywallDecline(let paywallInfo): Analytics.track("paywall_decline", properties: ["name": paywallInfo.name]) default: break } } // Handle custom HTML button actions (data-pw-custom tags) func handleCustomPaywallAction(withName name: String) { if name == "open_chat" { SupportChat.open() } else if name == "show_tutorial" { TutorialCoordinator.shared.start() } } // React to subscription status changes func subscriptionStatusDidChange( from oldValue: SubscriptionStatus, to newValue: SubscriptionStatus ) { print("Status changed from \(oldValue) to \(newValue)") } // Receive all SDK log output func handleLog( level: String, scope: String, message: String?, info: [String: Any]?, error: Error? ) { if level == "error" { ErrorReporter.log(message ?? "", context: scope) } } // Web paywall redemption func didRedeemLink(result: RedemptionResult) { switch result { case .subscribed(let info): print("Web subscription activated:", info.code) case .failed(let error): print("Redemption failed:", error.localizedDescription) default: break } } } // Attach in configure: let delegate = MySuperwallDelegate() Superwall.configure(apiKey: "pk_your_api_key") Superwall.shared.delegate = delegate ``` -------------------------------- ### Logging Source: https://github.com/superwall/superwall-ios/blob/develop/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md Configures and handles logging levels and messages. ```APIDOC ## `logLevel` ### Description Gets or sets the current log level for the SDK. ### Property `logLevel` (LogLevel) ``` ```APIDOC ## `SuperwallDelegate/handleLog(level:scope:message:info:error:)-9kmai` ### Description Delegate method called when the SDK generates a log message. ### Method `handleLog` ### Parameters - **level** (LogLevel) - The log level. - **scope** (LogScope) - The scope of the log message. - **message** (String) - The log message. - **info** (Dictionary?) - Additional information. - **error** (Error?) - An associated error, if any. ``` -------------------------------- ### SuperwallDelegate Protocol Source: https://context7.com/superwall/superwall-ios/llms.txt Implement the SuperwallDelegate protocol to receive lifecycle events, analytics, and custom action callbacks from the Superwall SDK. This allows you to track events, handle custom paywall actions, monitor subscription status changes, receive SDK logs, and process web paywall redemptions. ```APIDOC ## `SuperwallDelegate` Protocol Conforms to this protocol and assign to `Superwall.shared.delegate` to receive lifecycle events, analytics, and custom action callbacks. ```swift import SuperwallKit class MySuperwallDelegate: SuperwallDelegate { // Track all internal Superwall analytics in your own pipeline func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) { let params = eventInfo.params switch eventInfo.event { case .paywallOpen(let paywallInfo): Analytics.track("paywall_open", properties: [ "paywall_name": paywallInfo.name, "paywall_id": paywallInfo.identifier ]) case .transactionComplete(let transaction, let product, _, _): Analytics.track("purchase", properties: [ "product_id": product.productIdentifier, "transaction_id": transaction?.id ?? "" ]) case .paywallDecline(let paywallInfo): Analytics.track("paywall_decline", properties: ["name": paywallInfo.name]) default: break } } // Handle custom HTML button actions (data-pw-custom tags) func handleCustomPaywallAction(withName name: String) { if name == "open_chat" { SupportChat.open() } else if name == "show_tutorial" { TutorialCoordinator.shared.start() } } // React to subscription status changes func subscriptionStatusDidChange( from oldValue: SubscriptionStatus, to newValue: SubscriptionStatus ) { print("Status changed from \(oldValue) to \(newValue)") } // Receive all SDK log output func handleLog( level: String, scope: String, message: String?, info: [String: Any]?, error: Error? ) { if level == "error" { ErrorReporter.log(message ?? "", context: scope) } } // Web paywall redemption func didRedeemLink(result: RedemptionResult) { switch result { case .subscribed(let info): print("Web subscription activated:", info.code) case .failed(let error): print("Redemption failed:", error.localizedDescription) default: break } } } // Attach in configure: let delegate = MySuperwallDelegate() Superwall.configure(apiKey: "pk_your_api_key") Superwall.shared.delegate = delegate ``` ``` -------------------------------- ### Regenerate Xcode Project Source: https://github.com/superwall/superwall-ios/blob/develop/CLAUDE.md Execute this command to regenerate the Xcode project file from project.yml. This is often necessary after making changes to the project structure or dependencies. ```bash xcodegen ```