> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tryhelium.com/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS: Migrating from v3 to v4

> Migration guide for Helium iOS SDK v4

<Warning>
  **Breaking Changes in v4:**

  * `presentUpsell()` renamed to `presentPaywall()` with required `onPaywallNotShown` handler
  * Initialize parameters moved to `Helium.identify` and `Helium.config` namespaces
  * Fallback views removed - use fallback bundles only
</Warning>

<Info>
  **What's New:**

  * Simplified `initialize()` - just pass your API key
  * Automatic fallback bundle detection (`helium-fallbacks.json`)
  * Cleaner namespaced APIs: `Helium.identify`, `Helium.config`, `Helium.experiments`, `Helium.entitlements`
  * New `HeliumPaywall` SwiftUI component for embedded paywalls
  * **HeliumRevenueCat** is now a separate SPM package (CocoaPods unchanged)
</Info>

## Installation Update

Update your version constraint to `~> 4.0`:

<Tabs>
  <Tab title="Swift Package Manager">
    Update the **Dependency Rule** in Xcode to allow 4.x versions, or remove and re-add the package with the new version constraint.

    <Warning>
      **RevenueCat users:** HeliumRevenueCat is now a separate SPM package. After updating to v4, you may see a build error:

      ```
      Product 'HeliumRevenueCat' not found in package 'helium-swift'
      ```

      To fix this:

      1. In your target's **Frameworks, Libraries, and Embedded Content**, remove the broken **HeliumRevenueCat** reference
      2. Go to **Package Dependencies** and add the new package:
         ```
         https://github.com/cloudcaptainai/helium-swift-revenuecat.git
         ```
      3. Add the **HeliumRevenueCat** product from the new package to your app's main target
    </Warning>
  </Tab>

  <Tab title="Cocoapods">
    Update your Podfile:

    ```ruby theme={null}
    # Core only
    pod 'Helium', '~> 4.0'

    # Or with RevenueCat
    pod 'Helium/RevenueCat', '~> 4.0'
    ```

    Then run:

    ```bash theme={null}
    pod install
    ```
  </Tab>
</Tabs>

## Initialize Migration

The biggest change in v4 is the simplified initialization. Configuration that was previously passed to `initialize()` is now set on dedicated namespaces beforehand.

**v3 (many parameters):**

```swift theme={null}
// Deprecated v3 initialize
Helium.shared.initialize(
    apiKey: "...",
    heliumPaywallDelegate: RevenueCatDelegate(),
    customUserId: "user-123",
    customUserTraits: HeliumUserTraits(["plan": "free"]),
    fallbackConfig: .withFallbackBundle(bundleURL)
)
```

**v4 (simplified):**

```swift theme={null}
// Set up user identification
Helium.identify.userId = "user-123"
Helium.identify.setUserTraits(HeliumUserTraits(["plan": "free"]))
// If using RevenueCat
Helium.identify.revenueCatAppUserId = Purchases.shared.appUserID 

// Set up advanced configuration (if needed)
Helium.config.customFallbacksURL = Bundle.main.url(
    forResource: "fallback-bundle-2026-01-25",
    withExtension: "json"
)
Helium.config.purchaseDelegate = RevenueCatDelegate()

// Initialize with just API key
Helium.shared.initialize(apiKey: "...")
```

<Tip>
  Set identity and configuration values **before** calling `initialize()` for best results.
</Tip>

<Info>
  See the [quickstart](/sdk/quickstart-ios) for more details.
</Info>

## presentUpsell to presentPaywall

The method has been renamed and now requires an `onPaywallNotShown` handler.

<CodeGroup>
  ```swift v3 theme={null}
  Helium.shared.presentUpsell(trigger: "premium")
  ```

  ```swift v4 theme={null}
  Helium.shared.presentPaywall(trigger: "premium") { notShownReason in
      // handle paywall not shown
  }
  ```
</CodeGroup>

### Full Example with All Parameters

**v4:**

```swift theme={null}
Helium.shared.presentPaywall(
    trigger: "premium",
    config: PaywallPresentationConfig(
        presentFromViewController: viewController,
        customPaywallTraits: ["source": "settings"],
        dontShowIfAlreadyEntitled: true,
        loadingBudget: nil, // use default
    ),
    eventHandlers: PaywallEventHandlers()
        .onPurchaseSucceeded { event in
            print("Purchased!")
        },
    onEntitled: {
        // User is now entitled (purchased or restored)
    }
) { paywallNotShownReason in
    switch paywallNotShownReason {
    case .targetingHoldout:
        break
    case .alreadyEntitled:
        // User already has access
        break
    default:
        // Handle rare failure cases
        break
    }
}
```

## API Namespace Migration

### Helium.identify

| v3                          | v4                                          |
| --------------------------- | ------------------------------------------- |
| `initialize(customUserId:)` | `Helium.identify.userId = ...`              |
| `overrideUserId()`          | `Helium.identify.userId = ...`              |
| `getHeliumUserId()`         | `Helium.identify.userId`                    |
| `setAppAttributionToken()`  | `Helium.identify.appAccountToken = ...`     |
| `setRevenueCatAppUserId()`  | `Helium.identify.revenueCatAppUserId = ...` |

### Helium.config

| v3                                   | v4                                          |
| ------------------------------------ | ------------------------------------------- |
| `initialize(heliumPaywallDelegate:)` | `Helium.config.purchaseDelegate = ...`      |
| `setLightDarkModeOverride()`         | `Helium.config.lightDarkModeOverride = ...` |
| `Helium.restorePurchaseConfig`       | `Helium.config.restorePurchasesDialog`      |

### Helium.experiments

| v3                              | v4                                    |
| ------------------------------- | ------------------------------------- |
| `getExperimentInfoForTrigger()` | `Helium.experiments.infoForTrigger()` |
| `enrolledExperiments()`         | `Helium.experiments.enrolled()`       |
| `allExperiments()`              | `Helium.experiments.all()`            |

### Helium.entitlements

| v3                           | v4                                               |
| ---------------------------- | ------------------------------------------------ |
| `hasAnyEntitlement()`        | `Helium.entitlements.hasAny()`                   |
| `hasAnyActiveSubscription()` | `Helium.entitlements.hasAnyActiveSubscription()` |
| `hasEntitlementForPaywall()` | `Helium.entitlements.hasForPaywall()`            |
| `hasActiveEntitlementFor()`  | `Helium.entitlements.hasActiveFor()`             |
| `purchasedProductIds()`      | `Helium.entitlements.purchasedProductIds()`      |
| `activeSubscriptions()`      | `Helium.entitlements.activeSubscriptions()`      |

## Fallback Changes

v4 removes fallback views and simplifies fallback configuration.

### What's Changed

* **No more fallback views** - Use fallback bundles exclusively
* **Automatic detection** - SDK auto-detects `helium-fallbacks.json` in your main bundle
* **Optional override** - Use `Helium.config.customFallbacksURL` if needed

### Migration

**v3:**

```swift theme={null}
let fallbackConfig = HeliumFallbackConfig(
    fallbackView: YourFallbackView(),
    fallbackBundle: Bundle.main.url(
        forResource: "fallback-bundle",
        withExtension: "json"
    )
)

Helium.shared.initialize(
    apiKey: "...",
    fallbackConfig: fallbackConfig
)
```

**v4:**

```swift theme={null}
// Option 1: Automatic (recommended)
// Just add helium-fallbacks.json to your bundle - SDK finds it automatically

// Option 2: Custom location
Helium.config.customFallbacksURL = Bundle.main.url(
    forResource: "my-fallbacks",
    withExtension: "json"
)

Helium.shared.initialize(apiKey: "...")
```

<Info>
  Download your fallbacks from the Helium dashboard. See the [Fallback Bundle Guide](/guides/fallback-bundle) for details.
</Info>

## Embedded Paywall Views

The SwiftUI view for embedding paywalls has been replaced.

<CodeGroup>
  ```swift v3 (deprecated) theme={null}
  let paywallView = Helium.shared.upsellViewForTrigger("premium")
  ```

  ```swift v4 theme={null}
  HeliumPaywall(
      trigger: "premium",
      onPaywallNotShown: { reason in
          // Handle paywall not shown
      }
  )
  ```
</CodeGroup>

<Info>
  See [ways to show a paywall](/guides/ways-to-show-paywall) for more information.
</Info>

## Removed APIs

The following APIs have been removed in v4:

| Removed API                                | Replacement                            |
| ------------------------------------------ | -------------------------------------- |
| `getHeliumExperimentInfo()` (map version)  | `Helium.experiments.all()`             |
| `HeliumFallbackConfig` class               | Use `Helium.config.customFallbacksURL` |
| All `fallback*` parameters from initialize | Add `helium-fallbacks.json` to bundle  |
| `upsellViewForTrigger()`                   | `HeliumPaywall` component              |

## Quick Migration Checklist

Use this checklist to ensure you've covered all migration steps:

* Update SDK to 4.x (`~> 4.0`)
* Move user ID/traits to `Helium.identify.*` before `initialize()`
* Move delegate/config to `Helium.config.*` before `initialize()`
* Replace `presentUpsell()` with `presentPaywall()` and add required handler
* Set up fallback bundle (download from dashboard, add as `helium-fallbacks.json`)
* Update experiment calls to `Helium.experiments.*`
* Update entitlement calls to `Helium.entitlements.*`
* Replace `upsellViewForTrigger()` with `HeliumPaywall` component
* Test paywall presentation and purchases

## Need Help?

* Review the [iOS SDK Quickstart](/sdk/quickstart-ios)
* Contact support via Slack or [founders@tryhelium.com](mailto:founders@tryhelium.com)
