「Rork Max で SwiftUI ネイティブアプリを作るのは分かった。でも、App Store の中で見つけてもらって、毎月の収益にするまでにはまだ霧がかかっている」——個人開発者として、私自身もここで何度か止まってきました。コードが動くことと、App Store のカテゴリで上位に食い込んで収益を作ることの間には、想像より大きな段差があります。
幸い、私はこの段差を設計・実装・運用の3つに分けて考えることで何度か越えてきました。今回は、Rork Max でネイティブアプリを作り、App Store カテゴリ Top 100 を通り、月20〜50万円の収益を作るまでの完全手順を、個人開発者の視点で丁寧に書いていきます。
ここから先はコードと判断軸が中心になります。動くコードと「なぜそう設計するか」をセットでお届けしますので、自分のアプリにそのまま転用できる内容を目指しました。
App Store Top 100 を通すための設計指針
Top 100 に乗るアプリには、運や偶然を超えた共通の構造があります。私が継続的に観察してきた範囲では、3つの軸が揃っています。
1. 1日5分以内で価値を感じる体験:起動して5分以内に「これは便利」と思える瞬間がある 2. 毎日または毎週開く理由がある:ニュース性・進捗性・記録性のいずれかが組み込まれている 3. ストアの第一印象(スクリーンショットと説明文)が解像度高く伝わる:1秒でアプリの価値が伝わる
Rork Max は SwiftUI ネイティブの生成性能が強いので、1と2は実装スピードで作れます。3はテキストと画像のデザインの問題なので、最初に時間を投資すると後の伸びが大きく変わります。
実装に入る前に、私が必ず書き出すのは次の3つです。
- コア体験:起動から5分以内にユーザーが感じる「便利」を1文で書く
- 再訪トリガー:毎日・毎週開く理由を1つ決める(通知・記録蓄積・新着情報など)
- マネタイズフック:どこで広告が入り、どこで IAP/サブスクの提案が出るかを決める
この3つを書き終えてから初めて、Rork Max にコード生成を頼みます。これをやらずに「とりあえず作る」と、機能は揃うけど Top 100 には乗らないアプリができ上がります。
Rork Max でのプロジェクト構造とビルド設計
Rork Max は SwiftUI のネイティブコードを生成しますが、収益化を見越したプロジェクト構造に最初から整えておくと、後の機能追加が劇的にラクになります。私が今使っている構造はこうです。
MyApp/
├── App/
│ └── MyAppApp.swift
├── Features/
│ ├── Home/
│ ├── Detail/
│ └── Settings/
├── Monetization/
│ ├── AdManager.swift
│ ├── IAPManager.swift
│ └── SubscriptionManager.swift
├── Analytics/
│ └── AnalyticsTracker.swift
└── Resources/
└── Localizable.strings
Monetization/ ディレクトリを最初から切り出しておくのが肝です。広告・IAP・サブスクのコードがバラバラに散らばると、後の修正で必ずバグが入ります。全ての収益化ロジックを1つのディレクトリに集約しておくと、A/B テストの実装も後から差し込みやすくなります。
AdMob の SwiftUI 実装 — バナー・インタースティシャル・リワード
AdMob を SwiftUI で扱うときの肝は、広告表示のタイミングをユーザー体験から逆算することです。私が今使っている実装の基礎を載せます。
// AdManager.swift — AdMob ラッパー
import GoogleMobileAds
import SwiftUI
final class AdManager: NSObject, ObservableObject {
static let shared = AdManager()
@Published var isInterstitialReady: Bool = false
private var interstitialAd: GADInterstitialAd?
func setup() {
GADMobileAds.sharedInstance().start { _ in }
loadInterstitial()
}
private func loadInterstitial() {
let request = GADRequest()
GADInterstitialAd.load(
withAdUnitID: "ca-app-pub-XXXXXX/YYYYYY",
request: request
) { [weak self] ad, error in
if let error = error {
print("interstitial load failed: \(error.localizedDescription)")
self?.isInterstitialReady = false
return
}
self?.interstitialAd = ad
self?.isInterstitialReady = true
}
}
@MainActor
func showInterstitialIfReady(from rootViewController: UIViewController) {
guard let ad = interstitialAd else { return }
ad.present(fromRootViewController: rootViewController)
// 表示後は次の広告を先読み
loadInterstitial()
}
}
// バナー広告のSwiftUIラッパー
struct BannerAdView: UIViewRepresentable {
let adUnitID: String
func makeUIView(context: Context) -> GADBannerView {
let banner = GADBannerView(adSize: GADAdSizeBanner)
banner.adUnitID = adUnitID
banner.rootViewController = UIApplication.shared.connectedScenes
.compactMap { ($0 as? UIWindowScene)?.windows.first?.rootViewController }
.first
banner.load(GADRequest())
return banner
}
func updateUIView(_ uiView: GADBannerView, context: Context) {}
}ここで重要なのが、isInterstitialReady で表示可能性を見える化していることです。インタースティシャル広告は読み込みに時間がかかるので、未ロード状態で present を呼ぶと表示されません。状態を @Published で公開して、UI 側からも待ち合わせできるようにします。
「インタースティシャルを出す位置」も体験を左右します。私が観察してきた範囲では、起動直後・操作の途中ではなく、1つの作業を完了した直後(例: 記事を読み終わった、保存ボタンを押した)が、最も体験を壊しません。広告を出すたびに「ここで出す価値があるか?」を自問する習慣をつけると、ユーザーレビューが守れます。
IAP の実装 — StoreKit 2 で「広告を消す」買い切り
買い切り IAP は、AdMob と組み合わせるときに最も摩擦の少ない収益化です。「広告を消す」¥250〜¥480 の買い切りを提供すると、広告に疲れたユーザーが自然に課金する流れが作れます。
// IAPManager.swift — StoreKit 2
import StoreKit
final class IAPManager: ObservableObject {
static let shared = IAPManager()
@Published var products: [Product] = []
@Published var purchasedProductIDs: Set<String> = []
private let productIDs: [String] = ["com.example.myapp.removeads", "com.example.myapp.proupgrade"]
func loadProducts() async {
do {
self.products = try await Product.products(for: productIDs)
} catch {
print("loadProducts failed: \(error)")
}
}
func purchase(_ product: Product) async -> Bool {
do {
let result = try await product.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
purchasedProductIDs.insert(transaction.productID)
await transaction.finish()
return true
case .userCancelled, .pending:
return false
@unknown default:
return false
}
} catch {
print("purchase failed: \(error)")
return false
}
}
private func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
switch result {
case .verified(let safe):
return safe
case .unverified(_, let error):
throw error
}
}
func restorePurchases() async {
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result {
purchasedProductIDs.insert(transaction.productID)
}
}
}
}StoreKit 2 を使うことで、サーバーサイドのレシート検証なしでも比較的安全な実装が可能です。Transaction.currentEntitlements で復元できる作りにしておくと、機種変更後の復元クレームを防げます。
サブスクの設計 — 「無料で十分使える」ことを前提にした上位プラン
サブスクは、無料体験を壊さないことが最重要です。「サブスクに入らないと使えない」設計は、ストアの低評価レビューの直接的な原因になります。私の今のおすすめは、無料機能で 80%、サブスクで 20% の割合です。
サブスクで提供する20%の例:
- 広告完全非表示
- データ無制限保存(無料は直近30日まで)
- カスタムテーマ・上級フィルター
- 早期アクセス機能
// SubscriptionManager.swift — サブスク状態管理
import StoreKit
final class SubscriptionManager: ObservableObject {
static let shared = SubscriptionManager()
@Published var isSubscribed: Bool = false
@Published var subscriptionProducts: [Product] = []
private let groupID = "com.example.myapp.subscriptions"
func loadSubscriptions() async {
do {
let products = try await Product.products(for: [
"com.example.myapp.monthly",
"com.example.myapp.yearly"
])
self.subscriptionProducts = products
await refreshStatus()
} catch {
print("loadSubscriptions failed: \(error)")
}
}
func refreshStatus() async {
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result {
if transaction.productType == .autoRenewable {
isSubscribed = true
return
}
}
}
isSubscribed = false
}
func subscribe(_ product: Product) async -> Bool {
do {
let result = try await product.purchase()
if case .success(let verification) = result {
if case .verified(let transaction) = verification {
await transaction.finish()
await refreshStatus()
return true
}
}
return false
} catch {
print("subscribe failed: \(error)")
return false
}
}
}refreshStatus を起動時に必ず呼ぶことで、機種変更後やサブスク失効時にも UI が正しく追従します。サブスク中に広告を出してしまうのは、最も低評価につながる失敗パターンです。SubscriptionManager.shared.isSubscribed を AdMob 表示前に必ずチェックする習慣にしてください。
ASO(App Store 最適化)と最初の100ダウンロードまでの動き方
App Store 検索でアプリが見つけられるかどうかは、**タイトル・サブタイトル・キーワード(メタデータ)**の3つで8割が決まります。私が今使っている書き方の基準はこうです。
- タイトル:アプリ名 + 主要な動詞(例: 「PDF要約 — 議事録を3行に」)
- サブタイトル:誰の・どの仕事を・どう変えるかを30文字で(例: 「会議の議事録を1分で読める3行要約に」)
- キーワード:競合の少ない複合キーワードを最大100文字
最初の100ダウンロードを集めるための動きは、SNS 告知だけでは足りません。私が実際にやっているのは次の組み合わせです。
- note・X での開発記録投稿:機能ではなく「開発の判断と試行錯誤」を書く
- Reddit/Hacker News などコミュニティでの限定公開:完成度70%段階で意見を集める
- 個別 DM での友人・知人への直接案内:レビュー数の最初の10件を確保する
最初の10レビューが集まると、ストア検索の表示順位が大きく動きます。これが Top 100 への階段の最初の段差です。
リテンション設計 — Day 1 / Day 7 / Day 30 リテンションの目標値
App Store Top 100 の中で長期上位を維持するアプリには、リテンションの数字が共通しています。私が個人アプリで目標にしている水準はこうです。
- Day 1 リテンション: 40% 以上(インストール翌日も開いてくれる人の割合)
- Day 7 リテンション: 20% 以上
- Day 30 リテンション: 10% 以上
これを下回るアプリは、たとえダウンロード数が伸びても Top 100 内で滞留できません。リテンションを上げるための実装で最も効くのは、通知と進捗の見える化です。
通知は「個人開発のアプリだから難しい」と思われがちですが、UNUserNotificationCenter を使えば SwiftUI からシンプルに実装できます。重要なのは、通知の文面をユーザーごとにパーソナライズすることです。「明日も開きませんか?」という汎用文ではなく、「昨日記録した『朝5km走』の続きはどうですか?」のような、その人のデータに紐づいた一文が、開封率を3〜5倍に変えます。
よくある間違い・落とし穴
最後に、Top 100 を狙う上で気をつけたい落とし穴を3つ。
1. リリース直後に広告フォーマットを増やしすぎる バナー+インタースティシャル+リワードを最初から全部入れると、ユーザーレビューが荒れがちです。最初の2週間はバナーのみで運用し、評価が安定してからインタースティシャルを足すのが安全です。
2. ASO のキーワードを欲張りすぎる 高ボリュームキーワードばかり狙うと、競合に埋もれて全く検索表示されなくなります。月100〜500検索の中ボリューム複合キーワードを3〜5個拾う方が、Top 100 入りには現実的です。
3. サブスク中ユーザーへの広告表示
これは全ての低評価レビューの中で最も致命的なパターンです。isSubscribed のチェックを必ず最初に入れる、という鉄則を守るだけで、レビュー4.0台が4.5台に変わります。
全体を振り返って — Top 100 は『5分体験 × 再訪トリガー × 解像度の高いストア』の積み上げ
Rork Max で SwiftUI ネイティブアプリを作って App Store Top 100 に乗せ、月20〜50万円の収益にするのは、1つの飛躍ではなく、3つの掛け算で達成できます。
- 5分以内に価値を感じる体験
- 毎日・毎週開く理由
- ストア第一印象の解像度
実装の順序としては、まずコア体験を Rork Max で生成して半完成で出し、AdMob だけを入れて運用しながら ASO を育てるところから始めるのが現実的です。IAP とサブスクは、Day 1 リテンションが30%を超えてから足すと、課金率が大きく変わります。
料金面の判断軸を整理したい場合は、Rork Max の料金で SwiftUI ネイティブアプリを収益化する3つの判断 を先にお読みください。マイクロ単位のサブスク設計に踏み込みたい場合は、Rork で月10万円のアプリを作る — 価格とリテンション実装 も合わせてどうぞ。
最初の30分でやれるのは、自分のアプリの「5分体験を1文で」「再訪トリガーを1つ」「ストア第一印象のスクリーンショットを1枚」、この3つだけ書き出すことです。その3つが揃えば、Rork Max でのコード生成は驚くほどスムーズに進みます。