◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/ビジネス
⟐ ビジネス/2026-06-30中級

App Store のシステムメッセージを「いい瞬間」まで遅らせる設計

値上げ同意やウィンバック、請求エラーといった App Store のシステムメッセージは、既定では起動直後に割り込んできます。StoreKit 2 の Message を握り、オンボーディングや決済を邪魔しない瞬間まで表示を遅らせる設計を、動く Swift コードでまとめました。

StoreKit8アプリ内課金9Rork Max236収益化68UX5

✦ プレミアム記事

値上げの同意を求めるシートが、新規ユーザーのオンボーディングの3画面目で突然せり上がってきたことがありました。私自身、個人開発で課金のあるアプリをいくつか運用していますが、この割り込みが起きると、その回のオンボーディング完了率がはっきり落ちます。まだ価値を感じる前の人に「価格に同意してください」と迫っているのですから、当然かもしれません。

App Store のシステムメッセージ(値上げ同意・請求エラー・ウィンバックなど)は、既定ではアプリのフォアグラウンド復帰時に StoreKit が勝手に表示します。出すこと自体は避けられませんが、いつ出すかは開発側で握れます。StoreKit 2 の Message を購読し、表示の引き金を自分のタイミングで引く設計をまとめます。Rork Max が生成するネイティブ Swift を前提にしていますが、考え方は react-native-iap などをブリッジする場合にもそのまま使えます。

既定の挙動と、握れるポイント

理解の起点は「メッセージは保留できる」という一点です。StoreKit.Message.messages という非同期シーケンスを購読しておくと、システムが出そうとしたメッセージがアプリに流れてきます。ここで何もしなければ自動表示に任せることになりますが、シーケンスを受け取った時点で for await のループに入っていれば、表示は message.display(in:) を自分で呼ぶまで起きません。

つまり「購読する」イコール「自動表示を止めて、表示権をアプリ側に移す」ことになります。受け取ったメッセージはアプリが明示的に出すまで保持され、出さなければ次回起動時にまた届きます。取りこぼしの心配は要りませんが、永遠に出さないのは規約・売上の両面で不利なので、必ずどこかで出し切る設計にします。

メッセージの購読を起動時に1本だけ張る

購読はアプリ全体で1本に保ちます。複数箇所で for await を回すと、同じメッセージが二重に処理されかねません。@MainActor のコーディネーターを1つ用意し、起動直後にループを開始します。

import StoreKit
import SwiftUI
 
@MainActor
final class StoreMessageCoordinator: ObservableObject {
    /// 表示待ちのメッセージ(届いた時刻つき)
    private(set) var pending: [(message: Message, since: Date)] = []
    /// いま表示してよいか(画面状態で更新する)
    var canPresent: Bool = false { didSet { flushIfPossible() } }
 
    private var listenTask: Task<Void, Never>?
 
    func start() {
        guard listenTask == nil else { return }
        listenTask = Task { [weak self] in
            for await message in Message.messages {
                // ここで display を呼ばない限り、システムは自動表示しない
                self?.enqueue(message)
            }
        }
    }
 
    private func enqueue(_ message: Message) {
        // 値上げ同意と請求エラーは売上・規約に直結するので、保留が長引かないよう先頭へ
        switch message.reason {
        case .priceIncreaseConsent, .billingIssue:
            pending.insert((message, Date()), at: 0)
        default:
            pending.append((message, Date()))
        }
        flushIfPossible()
    }
 
    private func flushIfPossible() {
        guard canPresent, let scene = Self.activeScene() else { return }
        let next = pending
        pending.removeAll()
        for item in next {
            do { try item.message.display(in: scene) }
            catch { pending.append(item) } // 出せなければ戻して次の機会に
        }
    }
 
    private static func activeScene() -> UIWindowScene? {
        UIApplication.shared.connectedScenes
            .compactMap { $0 as? UIWindowScene }
            .first { $0.activationState == .foregroundActive }
    }
}

canPresent を画面状態に応じて切り替えるだけで、表示タイミングを完全に制御できます。ポイントは display(in:) が UIWindowScene を要求することです。シーンがまだ前面でない瞬間に呼ぶと失敗するので、foregroundActive なシーンが取れたときだけ出します。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦StoreKit 2 の Message.messages を購読し、message.display(in:) を呼ぶ瞬間を自分で選ぶ実装(動く Swift つき)
✦priceIncreaseConsent / billingIssue / generic の理由ごとに「すぐ出す・後に回す」を切り替える判断表
✦オンボーディング中・決済中・全画面再生中は保留し、ホームに戻った瞬間に出す保留キューの作り方
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

⟐ ビジネス2026-05-15
運用中の壁紙アプリをRork Maxで作り直した記録:工程別の実作業時間と再リリース後の指標
運用中の壁紙アプリをRork Maxでゼロから再実装し、工程ごとに実作業時間を計測しました。生成コードが取り違えた仕様、既存サブスクリプションの引き継ぎ手順、再リリース後30日のクラッシュ率と転換率まで数字で記録しています。
⟐ ビジネス2026-05-05
Rork MaxでiOSとAndroidを同時展開して収益を倍にする — デュアルストア収益化の設計
Rork Maxを使ってiOSとAndroidを同時に運用し、RevenueCatとAdMobで課金と広告を一元管理する収益設計。プラットフォーム別の価格設計・AdMobメディエーション・ATT対応・LTV計算まで、実運用の数字を交えて解説します。
⟐ ビジネス2026-05-05
Rork Max × RevenueCat Paywalls SDK で変わるペイウォール開発 — A/B テスト・リモート更新・コンバージョン改善の実装ガイド
RevenueCat Paywalls SDK を Rork Max アプリに統合し、審査なしでペイウォールをリモート更新・A/B テストする実装ガイド。コンバージョン率を改善するデザイン原則と実践コードをまとめます。