作るもの

このコードラボでは、ノーコードのリモートビルダーである RevenueCat Paywalls V2 でペイウォールを設計し、 RevenueCatUI を使って SwiftUI アプリでネイティブに表示します。ペイウォールをハードコードすることはありません。 ダッシュボードで作って公開するので、あとからアプリのアップデートなしに、あなた(またはマーケティングチーム)が変更できます。

ペイウォールの設計には、実際のペイウォールがどう作られているかを分析した RevenueCat の State of Subscription Apps 2026 のデータに基づいた選択を使います。

  • ペイウォールに2 つのプラン(もっとも一般的なレイアウトで、41〜60 パーセントのアプリが採用)。
  • 年額オプションに割引バッジを付けた強調プラン(74.5 パーセントのペイウォールが価格を強調)。
  • 無料トライアルの案内(54 パーセントのペイウォールに掲載)と「Cancel anytime」の安心感。
  • 「Continue」の CTA(もっとも一般的な行動喚起)。カウントダウンタイマーや進捗バーは使いません(上位のペイウォールにはほぼ見られません)。
前提条件: SDK がすでに構成され、pro エンタイトルメントを持つ RevenueCat プロジェクト。 はじめての場合は iOS コードラボ または SDK の構成ガイドから始めてください。

Paywalls V2 とは

Paywalls V2 は、ノーコードでリモート設定でき、完全にネイティブなペイウォールビルダーです。 RevenueCat ダッシュボードでビジュアルに設計すると、SDK がネイティブにレンダリングします(iOS では SwiftUI、Android では Jetpack Compose)。 リモートなので、アプリのアップデートを出さずにペイウォール全体を変更できます。

これが重要な理由は次のとおりです。

  • リリースなしで反復できる。文言、レイアウト、価格の強調、画像をダッシュボードから編集できます。
  • ネイティブである。Web ビューではなく、ネイティブコンポーネントでレンダリングし、ライト/ダークモードに合わせて表示します。
  • 実験に活かせる。ペイウォールは Offering に紐付くため、A/B テストできます(ステップ 10)。
バージョンについて。Paywalls V2 は 2025 年 6 月に GA になりました。V2 エディターで作ったペイウォールには purchases-ios 5.16.0 以降が必要です(ほかのプラットフォームでも最近の SDK バージョンが必要)。 従来のビルダーはレガシーペイウォールと呼ばれ、テンプレート変数名が異なります。

Offering とパッケージを設定する

ペイウォールは Offering 内のパッケージをレンダリングするので、まずそこから設定します (RevenueCat ダッシュボードのプロジェクト内)。

  1. ストア商品(月額と年額のサブスクリプション)が存在し、RevenueCat にインポートされているか確認します。
  2. 両方の商品を pro エンタイトルメントに紐付けます。
  3. デフォルトの Offering に、$rc_monthly$rc_annual の 2 つのパッケージを追加し、それぞれに対応する商品をラップします。
価格はストアから取得されます。パッケージはローカライズされた価格を持つストア商品をラップします。 ペイウォールは変数を通じてその価格を表示するため(次のステップ)、アプリに価格や商品 ID をハードコードすることはありません。

Offering とパッケージがはじめての場合は 商品と価格を取得するを参照してください。

エディターでペイウォールを設計する

ダッシュボードで Paywalls を開き、Create paywall をクリックします。テンプレートから始め(推奨)、エディターのペイウォールプロパティ(「Select an Offering for your Paywall」)で Offering に紐付けます。ここからデータに基づいたレイアウトを作ります。

  1. Package コンポーネント 2 つ:月額用と年額用。2 プランのペイウォールがもっとも一般的なレイアウトです。
  2. 年額プランを強調し、discount 変数を使って割引バッジを追加します。
  3. pro で何がアンロックされるかを示す Feature list と、短い見出しを追加します。
  4. 無料トライアルの案内「Cancel anytime」の一文を追加します。
  5. Purchase button(CTA)のラベルを 「Continue」に設定します。

テンプレート変数(二重中かっこ)を使うと、価格やオファーが常にストアから取得されます。

text
{{ product.price }}              -> "$9.99"          (the package price)
{{ product.price_per_period }}   -> "$59.99/year"    (price with the billing period)
{{ product.relative_discount }}  -> "37%"            (annual savings vs the priciest plan)
{{ product.offer_price }}        -> "free"           (the intro/trial price, when there is one)

満足できたら Publish をクリックします。アプリのアップデートなしに、すぐ公開されます。

小手先の演出は避ける。データによると、カウントダウンタイマーや進捗バーは上位のペイウォールにはほぼ見られません。 Countdown コンポーネントは存在しますが、必要ありません。明確なプラン、強調された年額オプション、 トライアルの一文があれば十分に機能します。

RevenueCatUI をインストールする

ペイウォールは RevenueCatUI ライブラリを通じてレンダリングされます。Xcode で Swift パッケージ https://github.com/RevenueCat/purchases-ios-spm.git を追加し、 RevenueCatRevenueCatUI両方のプロダクトを選択します。

swift
import SwiftUI
import RevenueCat     // the core SDK
import RevenueCatUI   // the paywall views
最小バージョン。RevenueCatUI は iOS 15.0 以降が必要で、Paywalls V2 は purchases-ios 5.16.0 以降が必要です。SDK はアプリ起動時にすでに構成されているはずです (ステップ 1 の前提条件を参照)。

presentPaywallIfNeeded でアクセスを制御する

ペイウォールを表示するもっとも簡単な方法は、presentPaywallIfNeeded view modifier です。 ユーザーがエンタイトルメントを持たないときにだけ Offering のペイウォールを表示し、 エンタイトルメントを得た時点で自動的に閉じます。

swift
import SwiftUI
import RevenueCat
import RevenueCatUI

struct RootView: View {
    var body: some View {
        ContentView()
            .presentPaywallIfNeeded(
                requiredEntitlementIdentifier: "pro",
                // For a hard paywall, add: presentationMode: .fullScreen
                purchaseCompleted: { customerInfo in
                    print("Purchased: \(customerInfo.entitlements.active.keys)")
                },
                restoreCompleted: { customerInfo in
                    // Dismisses automatically if "pro" is now active.
                    print("Restored: \(customerInfo.entitlements.active.keys)")
                }
            )
    }
}

ハードペイウォール(購入するまで先へ進めないもの)にするには、 presentationMode: .fullScreen で全画面表示します。データではハードペイウォールのほうがフリーミアムよりはるかにコンバージョンが高いため、 サブスクリプションが必要なコンテンツでよく選ばれます。

ペイウォールが未設定の場合は、エラーにはならず、SDK が Offering のパッケージを一覧するデフォルトのペイウォールを表示します。 そのため、設計を終える前でもこのコードは動作し、V2 ペイウォールを公開すると自動的に切り替わります。

シートで PaywallView を表示する

任意のタイミングでペイウォールを表示したいとき(たとえば「Upgrade」ボタンから)は、シートで PaywallView を表示します。現在の Offering のペイウォールをレンダリングします。

swift
struct SettingsView: View {
    @State private var showPaywall = false

    var body: some View {
        Button("Upgrade to Pro") { showPaywall = true }
            .sheet(isPresented: $showPaywall) {
                // No need to wrap this in a ScrollView, the paywall handles scrolling.
                PaywallView()
            }
    }
}

PaywallView() は現在の Offering を使います。特定の Offering を表示するには (この場合 A/B 実験のルーティングは無効になります)、明示的に渡します:PaywallView(offering: yourOffering)

コードで閉じるボタンを追加しない。displayCloseButton パラメータは V2 ペイウォールでは効果がありません。 閉じるボタンが必要なら、代わりにエディターでコンポーネントとして追加してください。

完了と閉じる操作を処理する

購入、復元、そしてユーザーがペイウォールを閉じようとする操作に反応するハンドラを付けます。

swift
.sheet(isPresented: $showPaywall) {
    PaywallView()
        .onPurchaseCompleted { customerInfo in
            // Verify access before unlocking; receiving CustomerInfo is not proof of entitlement.
            if customerInfo.entitlements.active["pro"]?.isActive == true {
                showPaywall = false
            }
        }
        .onRestoreCompleted { customerInfo in
            if customerInfo.entitlements.active["pro"]?.isActive == true {
                showPaywall = false
            }
        }
        .onRequestedDismissal {
            // Fires on close-button tap (or purchase completion).
            showPaywall = false
        }
}
必ずエンタイトルメントを確認する。完了ハンドラが CustomerInfo を渡してくれても、 それだけではユーザーがアクセス権を持つとはかぎりません。コンテンツをアンロックする前に customerInfo.entitlements.active["pro"]?.isActive を確認してください。PaywallViewonRequestedDismissal を使います(onDismiss modifier はありません)。

ほかのプラットフォームで表示する

同じダッシュボードのペイウォールが、あらゆるプラットフォームでレンダリングされます。表示 API は次のとおりです。

React Native

tsx
import RevenueCatUI, { PAYWALL_RESULT } from 'react-native-purchases-ui';

const result = await RevenueCatUI.presentPaywallIfNeeded({
  requiredEntitlementIdentifier: 'pro',
});
// result is PURCHASED, RESTORED, CANCELLED, ERROR, or NOT_PRESENTED

Android (Jetpack Compose)

kotlin
// paywallActivityLauncher is a PaywallActivityLauncher created in onCreate.
// Launch the paywall only if the entitlement is missing:
paywallActivityLauncher.launchIfNeeded(requiredEntitlementIdentifier = "pro")

Flutter

dart
import 'package:purchases_ui_flutter/purchases_ui_flutter.dart';

final result = await RevenueCatUI.presentPaywallIfNeeded("pro");
// result is a PaywallResult: notPresented, error, cancelled, purchased, restored
1 つの設計で、すべてのプラットフォームへ。ペイウォールはダッシュボードで定義するため、一度設計すれば iOS、Android、React Native、Flutter でネイティブにレンダリングされます。ペイウォール UI には Android API 24 以降が必要です。

テスト、A/B、まとめ

テスト

  1. pro を持たないユーザーとしてアプリを実行し、2 つのプラン、強調された年額オプション、トライアルの一文、「Continue」の CTA を備えたペイウォールが表示されるか確認します。
  2. サンドボックス購入を完了し、ペイウォールが閉じてコンテンツがアンロックされるか確認します。
  3. ダッシュボードでペイウォールを編集して公開し、再起動します。新しいビルドなしで変更が反映されます。

次は A/B テスト

本当の成果はテストから生まれます。ペイウォールは Offering に紐付くため、別のペイウォールを持つ 2 つ目の Offering を作り、 RevenueCat の Experiment を実行してどちらのコンバージョンが高いかを確かめられます。 さらに TargetingPlacements を使えば、オーディエンスごとに異なるペイウォールを配信できます。

作ったもの

データに基づいた 2 プランのレイアウトを持つ、リモートかつノーコードのネイティブペイウォールです。 presentPaywallIfNeededPaywallView で SwiftUI に表示し、 あらゆるプラットフォームでのレンダリングと実験への活用に対応しています。

次のステップ