Skip to main content
Using an older SDK version?If you’re upgrading from SDK v2.x, check our Migration Guide for breaking changes and upgrade instructions.

Background

Get set up with the Helium SDK for iOS. Reach out over your Helium slack channel or email founders@tryhelium.com for any questions.

Installation

Helium requires a minimum deployment target of iOS 15 and Xcode 14+. (Latest Xcode is recommended.)
We recommend using Swift Package Manager (SPM), but if your project primarily uses Cocoapods it might make sense to install the Helium Cocoapod instead.
  • Swift Package Manager (SPM)
  • Cocoapod
  1. In Xcode, navigate to your project’s Package Dependencies: Spm Add Pn
  2. Click the + button and search for the Helium package URL:
    https://github.com/cloudcaptainai/helium-swift.git
    
  3. Click Add Package.
  4. In the dialog that appears, make sure to add the Helium product to your app’s main target: Spm Target Pn
  5. (Optional) If you are using RevenueCat to manage purchases, we recommended you also add HeliumRevenueCat to your target so that you can use our RevenueCatDelegate referenced in the HeliumPaywallDelegate section of this guide. Otherwise leave as None for the HeliumRevenueCat row.
The HeliumRevenueCat target includes purchases-ios-spm as a dependency, not purchases-ios and you may encounter build issues if are using purchases-ios with SPM.
  1. Select Add Package in the dialog and Helium should now be ready for import.
  2. Version: We recommend using an upToNextMajor rule to make sure you get non-breaking bug fixes. The latest stable version of helium-swift is 3.0.5 : Xcode 3 Dependency

Initialize Helium

Initialize the Helium SDK as early as possible in your app’s lifecycle. Choose the appropriate location based on your app’s architecture:
  • SwiftUI
  • SceneDelegate
  • AppDelegate
@main
struct MyApp: App {
    init() {
        // Call Helium.shared.initialize here (see example below)
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
Add necessary imports:
import Helium
And initialize Helium in the location referenced above:
// Create fallback configuration
//   with a fallback bundle (recommended)
let fallbackBundleURL = Bundle.main.url(forResource: "fallback-bundle", withExtension: "json")
let fallbackConfig = HeliumFallbackConfig.withFallbackBundle(fallbackBundleURL)

//   or by providing a SwiftUI View
let fallbackConfig = HeliumFallbackConfig.withFallbackView(
    Text("Fallback shown")
)

Helium.shared.initialize(
    apiKey: "<your-helium-api-key>",
    fallbackConfig: fallbackConfig,
    revenueCatAppUserId: Purchases.shared.appUserID // RevenueCat ONLY
)
Helium.shared.initialize
method
Helium’s initialization is ran on a background thread, so you don’t have to worry about it blocking your app’s launch time. Helium will automatically retry downloads as needed for up to 90 seconds.
You can provide a custom user ID and custom user traits in the initialize method or by using Helium.shared.overrideUserId. Ideally this will be called before initialize is called to ensure consistency in analytics events.
Helium.shared.overrideUserId(
    newUserId: "<your-custom-user-id>",
    traits: HeliumUserTraits? = nil
);
In most cases there is no need to check download status. Helium will display a loading indication if a paywall is presented before download has completed.
You can check the status of the paywall configuration download using the Helium.shared.getDownloadStatus() method. This method returns a value of type HeliumFetchedConfigStatus, which is defined as follows:
public enum HeliumFetchedConfigStatus: String, Codable, Equatable {
    case notDownloadedYet
    case inProgress
    case downloadSuccess
    case downloadFailure
}
You can also simply check if paywalls have been successfully downloaded with Helium.shared.paywallsLoaded().
Retrieve basic information about the paywall ready to show for a specific trigger with Helium.shared.getPaywallInfo(trigger: String) which returns:
public struct PaywallInfo {
    public let paywallTemplateName: String
    public let shouldShow: Bool
}

HeliumPaywallDelegate

The HeliumPaywallDelegate can be passed in to Helium.shared.initialize otherwise the default StoreKitDelegate will be used. This delegate is responsible for handling the purchase logic for your paywalls and handling Helium Events. Use one of our pre-built delegates or create a custom subclass of HeliumPaywallDelegate.
  • StoreKitDelegate
  • RevenueCatDelegate
  • Custom Delegate
Use the StoreKitDelegate to handle purchases using native StoreKit 2:
import Helium

let delegate = StoreKitDelegate()
To handle Helium Events, simply create a subclass of StoreKitDelegate and override onPaywallEvent:
class MyStoreKitDelegate: StoreKitDelegate {
    override func onPaywallEvent(_ event: HeliumEvent) {
        switch event {
        case let openEvent as PaywallOpenEvent:
            // handle open event here
            break
        case let closeEvent as PaywallCloseEvent:
            break
        case let dismissEvent as PaywallDismissedEvent:
            break
        case let purchaseEvent as PurchaseSucceededEvent:
            break
        default:
            break
        }
    }
}
If you subclass StoreKitDelegate, make sure to pass in to Helium.shared.initialize!

Presenting Paywalls

You must have a trigger and workflow configured in the dashboard in order to show a paywall.
Now, anywhere in your iOS app, you can show a paywall in one of 3 ways. Call Helium.shared.presentUpsell(trigger:) when you want to show the paywall. For example:
Button("Try Premium") {
    Helium.shared.presentUpsell(trigger: "post_onboarding")
}
Helium.shared.presentUpsell
method

Attach it to a SwiftUI view as a ViewModifier

You can use the .triggerUpsell view modifier from any SwiftUI view:
struct ContentView: View {
    @State var isPresented: Bool = false
    var body: some View {
        VStack {
            Button {
                isPresented = true;
            } label: {
                Text("Show paywall")
            }

        }.triggerUpsell(isPresented: $isPresented, trigger: "post_onboarding")
    }
}

Explicitly embed the Helium Paywall View

You can also explicitly get the Helium paywall view via Helium.shared.upsellViewForTrigger. This method takes a trigger and returns the paywall as an AnyView.
let heliumView: AnyView? = Helium.shared.upsellViewForTrigger(
    trigger: "post_onboarding"
)
You’ll need to handle presentation and dismissal yourself if you use this way of displaying paywalls. You can handle dismissal with PaywallEventHandlers.

PaywallEventHandlers

When displaying a paywall you can pass in event handlers to listen for relevant Helium Events. You can chain a subset of handlers with builder syntax:
Helium.shared.presentUpsell(
    trigger: "post_onboarding",
    eventHandlers: PaywallEventHandlers()
        .onOpen { event in
            print("\(event.paywallName) via trigger \(event.triggerName)")
        }
        .onClose { event in
            print("\(event.paywallName) for trigger \(event.triggerName)")
        }
        .onDismissed { event in
            print("\(event.paywallName) for trigger \(event.triggerName)")
        }
        .onPurchaseSucceeded { event in
            print("\(event.paywallName) for trigger \(event.triggerName)")
        }
)
Only the onOpen, onClose, onDismiss, and `onPurchaseSucceeded events can be specified this way. For others, see the section on HeliumPaywallDelegate.
Event Timing:
  • onDismiss is triggered only when a user manually dismisses a paywall (from an X button, “no thanks”, etc.)
  • onClose is triggered both when the paywall is manually dismissed OR when a successful purchase triggers closing the paywall. (So any time the paywall is removed.)
  • onPurchaseSucceeded is triggered when a purchase completes successfully
Usage Guidelines:
  • Use onDismiss for post-paywall navigation when the paywall is dismissed but a user’s entitlement hasn’t changed
  • Use onPurchaseSucceeded for your post purchase flow (e.g., a premium onboarding navigation)
  • Use onClose when you need to handle logic when the paywall closed, regardless of reason

Checking Subscription Status & Entitlements

Check entitlements before showing paywalls to avoid showing a paywall to a user who should not see it.
The Helium SDK provides several ways to check user entitlements and subscription status. hasAnyEntitlement() Checks if the user has purchased any subscription or non-consumable product. hasAnyActiveSubscription(includeNonRenewing: Bool = true) Checks if the user has any active subscription. Set includeNonRenewing to false to check only auto-renewing subscriptions. hasEntitlementForPaywall(trigger: String, considerAssociatedSubscriptions: Bool = false) Checks if the user has entitlements for any product in a specific paywall. Returns nil if paywall configuration hasn’t been downloaded yet. hasActiveEntitlementFor(productId: String) Checks if the user has entitlement to a specific product. hasActiveSubscriptionFor(productId: String) Checks if the user has an active subscription for a specific product. hasActiveSubscriptionFor(subscriptionGroupID: String) Checks if the user has an active subscription in a specific subscription group. purchasedProductIds() Retrieves a list of all product IDs the user currently has access to. activeSubscriptions() Returns detailed information about all active auto-renewing subscriptions. subscriptionStatusFor(productId: String) Gets detailed subscription status for a specific product, including state information like subscribed, expired, or in grace period. subscriptionStatusFor(subscriptionGroupID: String) Gets detailed subscription status for a specific subscription group.

Fallbacks and Loading Budgets

If a paywall has not completed downloading when you attempt to present it, a loading state can show. By default, Helium will show this loading state as needed (a shimmer view for up to 2 seconds). You can configure, turn off, or set trigger-specific loading budgets. If the budget expires before the paywall is ready, a fallback paywall will show if available otherwise the loading state will hide and a PaywallOpenFailed event will be dispatched. The iOS sdk has 3 options for fallbacks:
  1. Fallback bundles
  2. Default fallback view
  3. Fallback view per trigger
All of this is configured via the HeliumFallbackConfig object passed in to initialize. Here are some examples:
// Just provde a fallback bundle
let fallbackBundleURL = Bundle.main.url(
    forResource: "fallback-bundle-xxxx-xx-xx",
    withExtension: "json"
)
let fallbackConfig = HeliumFallbackConfig.withFallbackBundle(fallbackBundleURL)

// Provide fallback view and configure loading budgets
let fallbackConfig = HeliumFallbackConfig(
    fallbackView: YourFallbackView(),
    // Global loading budget (in seconds)
    loadingBudget: 2.0,
    // Per-trigger loading budgets
    perTriggerLoadingConfig: [
        "onboarding": TriggerLoadingConfig(loadingBudget: 4),
        "quick_upgrade": TriggerLoadingConfig(useLoadingState: false),
    ]
)

Helium.shared.initialize(
    apiKey: "your-api-key",
    fallbackConfig: fallbackConfig
)

Testing

Docs here coming soon! After integration, please message us directly to get set up with a test app + in-app test support.
I