Rork Max が SwiftUI を生成するようになった当初は、正直「動くけれど App Store には出せない」コードがほとんどでした。それが 2026年春時点では、App Store 審査基準を意識した設計になっています。
私が2014年から個人アプリ開発をやってきた経験と、今年初めて Rork Max で App Store まで通したアプリの過程を、具体的なプロンプトとコード修正パターンで共有します。
Rork Max の SwiftUI 生成精度を理解する
まず知っておくべきは、Rork Max が生成する SwiftUI コードは「概要レベルの正確さ」です。つまり、アーキテクチャは正しいが、細部(State 管理、エラーハンドリング、Accessibility)では修正が必要な場合がほとんどです。
これを理解すれば、「Rork Max は自動化の道具ではなく、設計を高速化する道具」という使い方が見えてきます。
生成される SwiftUI の特徴
Rork Max が得意とするのは:
- UI レイアウト: VStack、HStack、ZStack の構成が正確
- 基本的な State 管理: @State、@ObservedObject の宣言が機能する
- データバインディング: TextField、Picker などのバインディング構文が正確
修正が必要になるのは:
- 複雑な State 管理: 複数の @State が互いに依存する場合、ロジックが重複・矛盾することがある
- 非同期処理: async/await の実装が正確さを欠き、Race Condition や キャンセル漏れが起きる
- Accessibility: VoiceOver 対応の
accessibilityLabelやaccessibilityHintがなく、見落とし多数
Rork Max で「破綻しない SwiftUI」を引き出す6つの設計指針
指針1: 画面設計を「State ツリー」として描写する
プロンプトで単に「ユーザーが タスクを入力できる画面を作って」と書くのではなく、その画面で管理すべき State を明示します。
悪い例:
タスク管理アプリを作ってください。ユーザーが新しいタスクを入力できる機能が必要です。
良い例:
タスク管理アプリの「新規タスク追加」画面を作ってください。
State:
- taskTitle: String(入力フィールドの値)
- selectedDate: Date(期日ピッカー)
- priority: Priority(enum: low, medium, high)
- isLoading: Bool(保存中フラグ)
- errorMessage: String?(エラー表示)
挙動:
- 空の状態では「保存」ボタンを無効化する
- 保存中は入力フィールドを無効化する
- エラー時は赤いテキストで errorMessage を表示する
この詳細度で書くことで、Rork Max が State の構造を正確に把握でき、生成コードの矛盾が劇的に減ります。
指針2: 非同期処理は「タスク」単位で分離する
Rork Max は async/await を使いますが、複数の async 処理が同じ関数内で動作すると、Race Condition を作りやすくなります。
// ❌ Rork Max が生成しやすい(危険)
func saveAndFetch() async {
isLoading = true
await saveTask()
await fetchTasks()
isLoading = false
}
// ✅ 修正版:タスク分離と キャンセル管理
@State private var savingTask: Task<Void, Never>?
@State private var fetchingTask: Task<Void, Never>?
func saveTask() async {
savingTask = Task {
isLoading = true
defer { isLoading = false }
try await api.save(task)
}
await savingTask?.value
}
func fetchTasks() async {
fetchingTask?.cancel()
fetchingTask = Task {
let tasks = try await api.fetch()
self.tasks = tasks
}
}プロンプトで「非同期処理は Task{ } で管理して、キャンセル可能にしてください」と明示することで、Rork Max の出力品質が上がります。
指針3: @StateObject と @EnvironmentObject を明確に分離する
Rork Max は時々、ローカル State と グローバル State を混在させます。
// プロンプト内で明示
// ローカル State: @State var isLoading: Bool
// グローバル State: AppState(@EnvironmentObject)をインジェクト
// AppState 内の変更:
// - appState.tasks の更新
// - appState.currentUser の参照このように明確に書くことで、Rork Max が State の責任範囲を正確に理解します。
指針4: エラーハンドリングを「3段階」で指定
Rork Max がよく忘れるのが、エラーの「種類」による使い分けです。
// ❌ よくある問題
if let error = error {
Text("エラーが発生しました")
}
// ✅ 指定すべき要件
// 1. ネットワークエラー:「インターネット接続を確認」と表示
// 2. バリデーションエラー:「タイトルは1文字以上入力」と表示
// 3. サーバーエラー:「もう一度お試しください」と表示プロンプトでこの3段階を列挙すると、Rork Max の出力に if-else チェーンで対応が含まれるようになります。
指針5: View が肥大化しないよう「責任分割」を指示
Rork Max は「1つの View に全機能」を詰め込みやすくなります。
// プロンプト内で明示
struct TaskListView: View {
// 記述:表示ロジックのみ
}
struct TaskListHeader: View {
// 記述:ヘッダー部のみ
}
struct TaskRowView: View {
// 記述:1行アイテムのみ
}
struct TaskListViewModel: ObservableObject {
// 記述:フェッチ・削除・フィルタリングの全ロジック
}この分割を事前に指示しておくと、Rork Max の出力がそれに従う傾向が強まります。
指針6: Accessibility を「最初から組み込む」指示
App Store 審査では Accessibility の不備で リジェクトされることも増えています。
// プロンプト内で明示
Text("タスク: \(task.title)")
.accessibilityLabel("タスク")
.accessibilityValue(task.title)
Button("削除") { deleteTask() }
.accessibilityLabel("タスクを削除")
.accessibilityHint("このアクションは取り消せません")「最初から accessibilityLabel と accessibilityHint を含めてください」と指示すると、Rork Max の出力にもそれが反映される傾向があります。
実装事例:完全なプロンプト履歴
私が App Store 通過まで使ったアプリの、実際のプロンプトを3段階で示します。
Batch 1: 基本設計(初稿要求)
Rork Max:以下のタスク管理アプリの初版を SwiftUI で生成してください。
【アプリの概要】
- ユーザーがタスクを追加・編集・削除できる
- タスクは「今日」「明日」「今週」の3つのセクションに分かれる
- 各セクションの下部には「セクション内の全完了数」を表示
【State 設計】
- tasks: [Task](全タスク配列)
- selectedDate: Date(現在のフォーカス日)
- isEditing: Bool(編集モード フラグ)
- editingTaskID: UUID?(編集中のタスク)
- showAddModal: Bool(新規追加モーダル表示)
【UI コンポーネント】
- TaskListView: 親コンテナ
- TaskSectionView: セクションごとの表示(「今日」「明日」「今週」)
- TaskRowView: 1行アイテム
- AddTaskModal: 新規・編集モーダル
【非同期処理】
- addTask() async: 新規タスクをサーバーに POST
- updateTask() async: 既存タスクを PATCH
- deleteTask() async: タスクを DELETE
生成時の注意:
- 各 View は責任を明確に分割する
- errorMessage: String? で エラー表示を管理する
- isLoading: Bool で ボタンと入力フィールドの無効化を制御する
Rork Max の出力:
- 基本的な View 構造が正確に生成される
- State の宣言が完全
- ただし非同期処理の詳細にエラーハンドリングが不足
Batch 2: 修正指示(非同期・エラーハンドリング改善)
Batch 1 の TaskListViewModel に以下の修正を加えてください:
【非同期処理の改善】
1. addTask() で以下の分岐を実装:
- ネットワークエラー:errorMessage = "インターネット接続を確認してください"
- バリデーションエラー:errorMessage = "タスク名は1文字以上入力してください"
- サーバーエラー(500以上):errorMessage = "サーバーエラー。もう一度お試しください"
2. deleteTask() でキャンセル管理を追加:
- deletingTaskID: UUID? で削除中タスクを追跡
- 削除中は該当行の操作ボタンを無効化
- 削除完了またはキャンセルで deletingTaskID = nil
3. fetchTasks() でキャンセル可能にする:
- @State private var fetchTask: Task<Void, Never>? で管理
- 新しいフェッチ要求時に fetchTask?.cancel() を実行
- Race Condition を防止
【エラーハンドリングの詳細】
// 以下のコード例を参考に実装
do {
let response = try await api.addTask(task)
tasks.append(response)
errorMessage = nil
} catch URLError.notConnectedToInternet {
errorMessage = "インターネット接続を確認してください"
} catch let validationError as ValidationError {
errorMessage = validationError.message
} catch {
errorMessage = "予期しないエラーが発生しました"
}
Rork Max の出力:
- エラーハンドリングが詳細化
- ただし Race Condition 対策が依然不完全な場合がある
- @State private var fetchTask の宣言は生成されるが、キャンセル実装が抜ける可能性
Batch 3: 最終調整(Accessibility・App Store 対応)
完成版に以下の要件を追加してください:
【Accessibility 対応】
全ての以下のテキスト・ボタンに accessibilityLabel を付与:
- タスク行:accessibilityLabel = "タスク: \(task.title), 期日: \(task.dueDate)"
- 完了チェック:accessibilityLabel = "完了", accessibilityHint = "このタスクを完了状態に切り替え"
- 削除ボタン:accessibilityLabel = "削除", accessibilityHint = "このアクションは取り消せません"
【App Store 審査対応】
1. プライバシーポリシーへのリンク:
- フッターに「プライバシーポリシー」を表示
- タップで Safari で https://example.com/privacy を開く
2. 利用規約へのリンク:
- フッターに「利用規約」を表示
- タップで Safari で https://example.com/terms を開く
3. お問い合わせリンク:
- 設定画面に「サポート」ボタンを追加
- メールアプリで support@example.com を開く
4. バージョン表示:
- 設定画面に「バージョン x.x.x」を表示
- Bundle.main.infoDictionary?["CFBundleShortVersionString"] から取得
【パフォーマンス対応】
- 100件以上のタスクを効率的に表示するため、TaskListView に LazyVStack を使用
- リスト外部の無駄なレンダリング削除
Rork Max の出力:
- accessibilityLabel は手厚く追加される
- App Store リンク(プライバシー・利用規約)が正確に生成される
- バージョン表示のバンドル取得も正確
実際の修正点:
// Rork Max の出力(Bundle取得)
Text("バージョン \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0")")
// 修正:型安全性向上
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
Text("バージョン \(version)")
}生成コードの典型的な落とし穴と修正テンプレート
落とし穴1: State の重複や矛盾
症状:
@State var taskName: String = ""
@State var taskTitle: String = "" // taskName と重複している修正テンプレート:
// 単一の真実源(Single Source of Truth)にまとめる
@State private var editingTask: Task = Task(id: UUID(), title: "", dueDate: Date())
// 変更時は editingTask 全体を更新
func updateTaskName(_ name: String) {
var updatedTask = editingTask
updatedTask.title = name
editingTask = updatedTask
}落とし穴2: 非同期処理の Race Condition
症状:
@State var fetchTask: Task<Void, Never>?
func refetch() async {
fetchTask = Task {
tasks = try await api.fetch() // 前のリクエストをキャンセルしない
}
}修正テンプレート:
@State var fetchTask: Task<Void, Never>?
func refetch() async {
fetchTask?.cancel() // 前のタスクをキャンセル
fetchTask = Task {
guard !Task.isCancelled else { return }
tasks = try await api.fetch()
}
}落とし穴3: エラー時の State リセット忘れ
症状:
func deleteTask() async {
isLoading = true
do {
try await api.deleteTask(taskID)
} catch {
errorMessage = "削除に失敗しました"
// isLoading = false が抜けている
}
}修正テンプレート:
func deleteTask() async {
isLoading = true
defer { isLoading = false } // 成功・失敗どちらでも必ず実行
do {
try await api.deleteTask(taskID)
} catch {
errorMessage = "削除に失敗しました"
}
}落とし穴4: Accessibility の VoiceOver 対応不足
症状:
HStack {
Image(systemName: "checkmark.circle") // VoiceOver が読み上げられない
Text(task.title)
}修正テンプレート:
HStack {
Image(systemName: "checkmark.circle")
.accessibilityHidden(true) // 装飾なので隠す
Text(task.title)
}
.accessibilityElement(children: .combine) // テキストだけ読み上げ
.accessibilityLabel("完了済み:\(task.title)")落とし穴5: List/LazyVStack でのキーの不適切な使用
症状:
List(tasks) { task in // id を明示していない
TaskRowView(task: task)
}修正テンプレート:
List(tasks, id: \.id) { task in // UUID や id フィールドで明示
TaskRowView(task: task)
}
// または
LazyVStack(spacing: 8) {
ForEach(tasks, id: \.id) { task in
TaskRowView(task: task)
}
}App Store 申請で私が直面した落とし穴
1. プライバシーポリシー URL がハードコードのまま
App Store 審査で「プライバシーポリシーの URL が app.example.com になっていて、実在しない」というリジェクトをもらいました。
修正:
// ❌ Rork Max が生成(ダミー)
Link("プライバシーポリシー", destination: URL(string: "https://app.example.com/privacy")!)
// ✅ 修正版:実際の URL に置換
Link("プライバシーポリシー", destination: URL(string: "https://mycompany.com/privacy")!)2. Accessibility の最低基準に達していない
VoiceOver テストで「Image が読み上げられない」というリジェクトが返ってきました。
修正:
// ❌ Rork Max が生成
Image("icon")
// ✅ 修正版:最低限の accessibilityLabel
Image("icon")
.accessibilityLabel("アプリアイコン")
.accessibilityHidden(task.isDecorative) // 装飾なら隠す3. Crash Log への対応不足
beta テスター報告で「特定条件で Crash」が判明。Rork Max の出力に Force Unwrap(!)が複数ありました。
修正:
// ❌ Rork Max が生成(危険)
let url = URL(string: imagePath)! // crash の可能性
// ✅ 修正版:ガード句
guard let url = URL(string: imagePath) else {
errorMessage = "画像 URL が無効です"
return
}4. App Store 連絡先フィールドが空
審査チームが「サポート連絡先がない」とリジェクト。Rork Max はこのフィールド自体を生成していませんでした。
修正: 設定画面に以下を追加:
Section("サポート") {
Link("お問い合わせ", destination: URL(string: "mailto:support@example.com")!)
}継続開発フローへの組み込み方
App Store リリース後、機能追加時の Rork Max 活用パターン。
パターン1: 新しい Screen の追加
【既存】
- TaskListView:タスク一覧
- TaskDetailView:タスク詳細
- AddTaskModal:新規作成モーダル
【追加要件】
TaskFiltersView を追加:
- フィルタ種別:「カテゴリ」「優先度」「期日範囲」
- State:selectedCategory, selectedPriority, dateRange
- TaskListView に filter(by:) を呼び出し
このように既存構造を明示した上でプロンプトを出すと、Rork Max は一貫性のあるコードを生成します。
パターン2: API 仕様変更への対応
【既存】
Task 構造体:
- id: UUID
- title: String
- dueDate: Date
【変更】
Task 構造体:
- id: UUID
- title: String
- dueDate: Date
- tags: [String](新規)
- isArchived: Bool(新規)
【修正要件】
1. Task 構造体に tags, isArchived を追加
2. TaskRowView で tags を小さなバッジで表示
3. TaskListView で isArchived == false のみフィルタ
4. API 呼び出しで tags と isArchived を送受信
次に試すべきこと
1. Rork Max の学習フィードバック
Rork Max で生成したコードが App Store を通った場合、その成功パターンをプロンプトに組み込むと、次世代の生成品質が上がります。私が「非同期処理は Task で管理」と指示するようになったのも、過去の成功パターンを体系化したからです。
2. ローカルテストの自動化
Xcode の XCTest と組み合わせて、Rork Max が生成したコードの自動テストを組むことで、修正が必要な箇所をすぐに発見できます。
final class TaskListViewModelTests: XCTestCase {
func testAddTaskValidation() async {
let viewModel = TaskListViewModel()
await viewModel.addTask(Task(title: "", dueDate: Date()))
XCTAssertEqual(viewModel.errorMessage, "タスク名は1文字以上入力してください")
}
}3. Accessibility 自動監査
VoiceOver に頼らず、アクセシビリティの機械的な監査ツール(AccessibilityInspector)を組み込むことで、修正漏れを防止できます。
4. Version ごとのプロンプト進化
Rork Max のバージョンが上がるたびに、新しいプロンプト仕様が有効になります。定期的にプロンプトを見直し、最新の言語機能や API を活用することが大切です。
結論:Rork Max は「初期設計者」である
Rork Max を使い始めて感じるのは、このツールは「完全な自動化」ではなく、開発プロセスの「設計フェーズを高速化する」ものだということです。
2014年から個人アプリ開発をやってきた経験では、SwiftUI コードの品質は「初期設計がどれだけ精密か」で9割決まります。Rork Max は、その初期設計を 1/10 の時間で作ってくれる。残り 1割の修正(エラーハンドリング、Accessibility、App Store 対応)は、開発者の責務です。
この関係を理解した上で Rork Max と付き合えば、App Store デビューまでの時間は大幅に短縮できます。