Rork Max が SwiftUI コードを吐き出すまでの速度は、本当に驚くものがあります。チャット欄に「カウンタ機能付きの瞑想タイマーアプリ」と書けば、数分で動くプロジェクトが手元に来ます。
問題はその後です。私はこの2年で Rork や類似ツール経由のコードを5本リリースしましたが、生成された瞬間のコードがそのまま App Store を通過したことは一度もありません。生成コードと「ストアで公開できる商品」の間には、目には見えにくい工程が確実に存在します。本稿は、その工程を初日から逆算して進めるためのプレイブックです。
生成直後の初日チェックリスト
Rork Max からプロジェクトをダウンロードしたら、Xcode で開いてビルドする前に、12項目を順に確認します。これを最初にやらないと、後工程でやり直しが発生します。
ひとつ目は Bundle Identifier の修正 です。Rork が初期生成する com.rork.example.MyApp のような ID をそのまま使うと、App Store Connect 登録時に確実に弾かれます。com.{あなたのドメインを逆順}.{アプリ名} の形式で、最終的な公開名を最初から決めてしまいます。後から変える方が明確に手間です。
ふたつ目は Deployment Target の確認 です。Rork は最新 iOS を前提に生成しますが、私の経験ではターゲットを iOS 17 以降に設定するのが現実的です。iOS 18 限定にすると、ユーザー対象が一気に狭まります。古い API を使う必要が出たら、その都度 if #available(iOS 18.0, *) で分岐します。
3つ目は Asset Catalog の構造化 です。生成直後は Assets.xcassets に画像がフラットに並んでいることが多いので、Branding/、Icons/、Illustrations/ のように分類しておきます。これだけで後の差し替え作業が劇的に楽になります。
4つ目以降は、SwiftUI のプレビューが全 View で動くかチェック、@MainActor の付け忘れがないか、Color 指定がダークモード対応かどうか、LocalizedStringKey を使っているか、accessibility(label:) が主要要素に付いているか、ビューの初期化で重い処理を走らせていないか、@StateObject と @ObservedObject の使い分けが正しいか、Task の中で UI 更新を MainActor.run でラップしているか、Info.plist の LSApplicationCategoryType を設定したか、UIRequiredDeviceCapabilities を最低限に絞ったか、までを順に確認します。
この12項目は、後から直そうとすると半日仕事になりますが、初日にやれば30分で終わります。
SwiftUI設計パターン — Rork生成コードの典型的な弱点
Rork Max の生成コードには、構造的に同じパターンの弱点があります。これを理解しておくと、生成された瞬間に「ここは書き直す」と判断できます。
最大の弱点は、ContentView がモノリシックになりがち なことです。生成コードでは、画面遷移・データ取得・UI ロジックがすべて1つの View に詰まっていることが多く、機能を追加するたびに ContentView が膨らみます。最初に必ず以下のような分離を行います。
// 生成直後のContentView(典型例)
struct ContentView: View {
@State private var items: [Item] = []
@State private var isLoading = false
@State private var showSettings = false
var body: some View {
NavigationStack {
// 100行以上のロジック...
}
}
}
// 分離後
struct ContentView: View {
@StateObject private var viewModel = ContentViewModel()
var body: some View {
NavigationStack {
ItemListView(items: viewModel.items)
.toolbar { ToolbarContent(viewModel: viewModel) }
.task { await viewModel.load() }
}
}
}
@MainActor
final class ContentViewModel: ObservableObject {
@Published var items: [Item] = []
@Published var isLoading = false
func load() async {
isLoading = true
defer { isLoading = false }
// 取得処理
}
}ふたつ目の弱点は、エラーハンドリングが省略されがち なこと。生成コードは「ハッピーパス」だけが綺麗に書かれていて、ネットワークエラーや権限拒否時の挙動が print("error: \(error)") だけで止まっていることが多いです。リリース前には必ず、ユーザー向けエラーメッセージをローカライズして表示する仕組みを入れます。
3つ目は、プレビューが本番データを参照している ケースです。#Preview 内で MainActor.assumeIsolated を使ったり、ネットワーク呼び出しが走ったりすると、Xcode の動作が重くなります。プレビュー用のモックデータを別ファイルに切り出しておくと、開発体験が大きく改善します。
CoreData / SwiftData との統合の落とし穴
Rork Max は SwiftData での生成を好みますが、実プロジェクトに組み込むときには注意点が複数あります。
最大の罠は、スキーマ変更時のマイグレーション戦略が考慮されていない ことです。生成直後のモデルはそのまま使えますが、リリース後に「カラムを1つ足したい」となった瞬間に、既存ユーザーのデータが消える事故が起きます。最初から VersionedSchema を導入し、マイグレーションプランを定義しておきます。
enum AppSchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] {
[Item.self]
}
@Model
final class Item {
var title: String
var createdAt: Date
init(title: String, createdAt: Date = .now) {
self.title = title
self.createdAt = createdAt
}
}
}
enum AppMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[AppSchemaV1.self]
}
static var stages: [MigrationStage] { [] }
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup { ContentView() }
.modelContainer(for: AppSchemaV1.Item.self, migrationPlan: AppMigrationPlan.self)
}
}最初は空のマイグレーションプランで構いません。重要なのは「将来マイグレーションが必要になったときに足せる構造」を最初から持っておくことです。
CoreData を選ぶ場合は、Rork 生成コードがしばしば古いパターン(NSManagedObject のサブクラスを手書き)を使うことがあるので、最新の @FetchRequest プロパティラッパーに置き換えます。これで SwiftUI との相性が一気に良くなります。
StoreKit 2 と課金実装
私の運営アプリの収益はほぼ広告ですが、サブスクリプション課金を入れているアプリもあります。Rork Max は StoreKit 2 のコードも生成してくれますが、商用品質に持っていくには追加実装が必要です。
最初に必須なのは トランザクションリスナー です。アプリ起動時にバックグラウンドで未処理トランザクションを処理する仕組みを必ず入れます。これがないと、ユーザーが課金してアプリを落とした瞬間に権限が反映されない事故が起きます。
@MainActor
final class StoreManager: ObservableObject {
@Published private(set) var entitledProductIDs: Set<String> = []
private var updatesTask: Task<Void, Never>?
init() {
updatesTask = Task { await listenForUpdates() }
Task { await refreshEntitlements() }
}
private func listenForUpdates() async {
for await result in Transaction.updates {
await handle(result: result)
}
}
private func handle(result: VerificationResult<Transaction>) async {
guard case .verified(let transaction) = result else { return }
await refreshEntitlements()
await transaction.finish()
}
func refreshEntitlements() async {
var ids: Set<String> = []
for await result in Transaction.currentEntitlements {
if case .verified(let t) = result {
ids.insert(t.productID)
}
}
entitledProductIDs = ids
}
}審査でよく弾かれるのは、「購入を復元」ボタンの欠如 です。Apple のガイドラインで明示的に要求されていて、設定画面のどこかに必ず置く必要があります。AppStore.sync() を呼ぶだけのシンプルなボタンですが、これがないと審査でリジェクトされます。
TestFlight 準備とベータテスター運用
TestFlight でのベータテストは、本審査を通すために必須の工程です。私は新規アプリでは最低でも2週間、20人以上のテスターで回すようにしています。
ベータテスター集めで効くのは、SNSでの募集ではなく、既存アプリのユーザーへのアプリ内告知 です。私が運用している壁紙アプリ『綺麗な壁紙』では「次の新作のテスターを募集中」という小さなバナーを置いていて、毎回30〜50人が手を挙げてくれます。これは新作の品質を保つ実用的な仕組みです。
TestFlight ビルドのアップロード時には、Crash 検出を必ず有効化 しておきます。Xcode Organizer から確認できますが、外部ツール(Sentry や Firebase Crashlytics)を入れておくと、ベータ期間中のクラッシュ修正が劇的に楽になります。私は新規プロジェクトには必ず Sentry を入れています。
ベータビルドのリリースノートには、テスターに見てほしい機能を明示します。「この機能の挙動について意見をください」とテキストで伝えるだけで、フィードバックの質が劇的に変わります。
審査で実際に弾かれた事例と対処
私が運営する10本以上のアプリで、リジェクトされた事例から特に Rork 系生成コードに起きやすいものを3つ紹介します。
事例1:プライバシーマニフェストの不足
iOS 17 以降、PrivacyInfo.xcprivacy ファイルが必須化されました。Rork 生成コードはこのファイルを含まないことが多く、初回審査で弾かれます。プロジェクトのルートに以下のような最小ファイルを追加します。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>使用している API カテゴリ(UserDefaults、FileTimestamp、SystemBootTime など)に応じて Reason コードを追加していきます。
事例2:購入復元ボタンの不足
前述の StoreKit 2 セクションで触れた通りですが、これは Rork 生成コードでよく抜け落ちます。設定画面に「購入を復元」ボタンを追加して再申請します。
事例3:プレースホルダー画像のままリリース
これは生成コードあるあるですが、Image(systemName: "photo") などのプレースホルダーが残ったまま提出してしまい、審査担当者から「機能が不完全」と判断されてリジェクトされた事例があります。リリース前に、生成コード内の systemName: 検索をかけて、本来のアセットに置き換わっているか必ず確認します。
ASO と初週 KPI の設計
公開しただけでは誰にも気づいてもらえません。私の経験では、初週の動きを設計しておかないと、その後の伸びが3倍以上違います。
スクリーンショットは、最初の3枚に投資を集中 します。App Store の検索結果では3枚目までしか見えないことが多く、ここで決まります。Rork が生成したアプリのスクリーンショットを貼るだけでなく、上にコピーを乗せた「マーケティング画像」として作り込みます。
キーワード設定では、自分のアプリ名以外に、関連する一般語を含めます。100文字制限 の中に詰め込めるだけ詰め込みますが、カンマ区切りで連続させた方が認識率が高いという報告もあります。私はカンマ区切りでひたすら関連語を並べる派です。
初週 KPI として最も重要なのは、初日インストール数 ではなく 3日後の継続率(D3 Retention) です。初日インストール数は広告で買えますが、D3 が低いとアルゴリズムから低品質と判定され、その後のオーガニック露出が伸びません。最低でも D3 30% を目指します。
リリース後の運用
App Store にリリースしたら終わり、ではありません。最初の1ヶ月で必ずやるべきことが3つあります。
ひとつ目は ユーザーレビューへの返信 です。星3以下のレビューには48時間以内に必ず返信します。これは ASO アルゴリズムにも好影響があり、レビュー全体の評価が上がっていきます。
ふたつ目は クラッシュ率の監視 です。Xcode Organizer の Crashes タブを毎日見て、新規クラッシュが出たら即座にホットフィックスをリリースします。Rork 生成コードは初回リリースで予期しないエッジケースが見つかりやすいので、最初の2週間は毎日確認するくらいで丁度いいです。
3つ目は アップデート頻度の維持 です。リリース後30日間で最低2回はアップデートを出します。これは「活発に開発されているアプリ」というシグナルを Apple のアルゴリズムに送る効果があり、検索順位に影響します。
最後に — Rork Max を「最初の一歩」として活かす
Rork Max が生成するコードは、リリース可能な状態の60〜70%だと私は捉えています。残りの30〜40%は、本稿で触れたような「本物のアプリにするための工程」で、ここを省略すると審査を通過しないか、通過してもユーザーから「未完成」と評価されます。
ただ、最初の60〜70%を数分で得られる価値は計り知れません。私自身、Rork のおかげで企画から App Store 公開までの所要日数が半分以下になりました。本稿のプレイブックを横に置きながら、あなたのアプリを世に出してみてください。