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
In Xcode, navigate to your project’s Package Dependencies:
Click the + button and search for the Helium package URL:
For Dependency Rule we recommend the default Up to Next Major Version to make sure you get non-breaking bug fixes. View the list of releases here.
Click Add Package.
In the dialog that appears, make sure to add the Helium product to your app’s main target:
(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 Purchase Handling section of this guide. Otherwise leave as None for the HeliumRevenueCat row.
The HeliumRevenueCat target includes purchases-ios-spm as a dependency, notpurchases-ios and you may encounter build issues if are using purchases-ios with SPM. (We recommend just switching to purchases-ios-spm).
Select Add Package in the dialog and Helium should now be ready for import.
(Optional) If set, a custom user id to use instead of Helium’s. We’ll use this id when forwarding to third party analytics services, so this can be used for attribution (e.g. an amplitude user id, or a custom user id from your own analytics service)
(Optional) Pass in custom user traits to be used for targeting, personalization, and dynamic content. It can be created with any dictionary, as long as the key is a string and the value is a Codable type.
Copy
Ask AI
let customUserTraits = HeliumUserTraits(traits: [ "testTrait": 1, "account_age_in_days": "20",])
(Optional) RevenueCat ONLY. Supply RevenueCat appUserID here (and initialize RevenueCat before Helium initialize). You can also set this value outside of initialize: Helium.shared.setRevenueCatAppUserId(Purchases.shared.appUserID)
No need to set this if you use RevenueCatDelegate (see next section).
Helium’s initialization is ran on a background thread, so you don’t have to worry about it affecting your app’s launch time.
Custom User ID and Custom User Traits
You can provide a custom user ID and custom user traits in the initialize method or by using Helium.shared.overrideUserId. Set the user ID and traits before or during initialize to ensure consistency in analytics events and for the best experimentation results.
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:
Copy
Ask AI
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().
(Optional) If true, the paywall will not show if the user already has entitlements for any product in the paywall. See the Entitlements section of this guide for more details.
Looking for alternative presentation methods? Check out the guide on Ways to Show a Paywall.
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:
Copy
Ask AI
Helium.shared.presentUpsell( trigger: "post_onboarding", eventHandlers: PaywallEventHandlers() .onOpen { event in print("open via trigger \(event.triggerName)") } .onClose { event in print("close for trigger \(event.triggerName)") } .onDismissed { event in print("dismiss for trigger \(event.triggerName)") } .onPurchaseSucceeded { event in print("purchase succeeded for trigger \(event.triggerName)") } .onOpenFailed { print("open failed for trigger \(event.triggerName)") } .onCustomPaywallAction { event in print("Custom action: \(event.actionName) with params: \(event.params)") } .onAnyEvent { event in // A handler for all paywall-related events. // Note that if you have other handlers (i.e. onOpen) set up, // both that handler AND this one will fire during paywall open. })
Usage Suggestions:
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 to handle a paywall close, regardless of reason
You should now be able to see Helium paywalls in your app! Well done! 🎉
By default, Helium will handle purchases for you! This section is for those who want to delegate purchases to RevenueCat or implement custom purchase logic.
Use (or subclass) one of our pre-built HeliumPaywallDelegate implementations or create a custom delegate. Pass the delegate in to your Helium.shared.initialize call.
StoreKitDelegate
RevenueCatDelegate
Custom Delegate
The StoreKitDelegate (default delegate) handles purchases using native StoreKit 2:
Copy
Ask AI
import Heliumlet delegate = StoreKitDelegate()
Make sure you included HeliumRevenueCat (for SPM) or Helium/RevenueCat (for Cocoapod) as noted in the Installation section.
RevenueCatDelegate handle purchases through RevenueCat:
Copy
Ask AI
import HeliumRevenueCat // unless using Cocoapod then can just import Heliumlet delegate = RevenueCatDelegate( revenueCatApiKey: "<revenue-cat-api-id>" // Optional - pass in to have Helium to handle RevenueCat initialization.)
If you do not supply revenueCatApiKey, make sure to initialize RevenueCat before initializing Helium!
You can also create a custom delegate and implement your own purchase logic. You can look at our StoreKitDelegate and RevenueCatDelegate in the SDK for examples (also linked below).The HeliumPaywallDelegate is defined as follows:
Copy
Ask AI
public protocol HeliumPaywallDelegate: AnyObject { // Execute the purchase of a product given the product ID. func makePurchase(productId: String) async -> HeliumPaywallTransactionStatus // (Optional) - Restore any existing subscriptions. // Return a boolean indicating whether the restore was successful. func restorePurchases() async -> Bool // (Optional) - Handle Helium Events func onPaywallEvent(_ event: HeliumEvent)}
HeliumPaywallTransactionStatus is an enum that defines the possible states of a paywall transaction:
Copy
Ask AI
public enum HeliumPaywallTransactionStatus { case purchased case cancelled case failed(Error) case restored case pending}
Visit Helium Events for details on the different Helium paywall events.
When executing the purchase via StoreKit 2 (recommended over StoreKit 1), please use Product.heliumPurchase() instead of Product.purchase(). For example:let result = try await product.heliumPurchase()This will automatically set attribution information for revenue tracking.
StoreKitDelegate example here.RevenueCatDelegate example here.
Subclass one of the provided HeliumPaywallDelegate implementations (see Purchase Handling section above) and override onPaywallEvent:
Copy
Ask AI
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 } }}
Make sure to pass your delegate in to Helium.shared.initialize!
/// Implement this where you want to handle eventspublic protocol HeliumEventListener : AnyObject { func onHeliumEvent(event: HeliumEvent)}/// Add a listener for all Helium events. Listeners are stored weakly, so if you create a listener inline it may not be retained.public func addHeliumEventListener(_ listener: HeliumEventListener)/// Remove a specific Helium event listener.public func removeHeliumEventListener(_ listener: HeliumEventListener)
The Helium SDK provides several ways to check user entitlements and subscription status.
Entitlement Helper Methods
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.
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 7 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:
Retrieve basic information about the paywall for a specific trigger with Helium.shared.getPaywallInfo(trigger: String) which returns:
Copy
Ask AI
public struct PaywallInfo { public let paywallTemplateName: String // shouldShow only false if the paywall should not be shown due to targeting or workflow configuration (Helium handles this for you in presentUpsell) public let shouldShow: Bool}
This method can be used if you want to be certain that a paywall is ready for display before displaying.
Reset Helium
Reset Helium entirely so you can call initialize again. Only for advanced use cases.