HealthKit を使ったフィットネストラッカーを Rork Max で作り始めたとき、最初のプロンプトで歩数グラフが動いたときは「これはいける」と思いました。ところが睡眠データを追加しようとした瞬間から、生成されるコードが微妙にずれ始めました。HKSleepAnalysis の取り扱いが微妙に古く、承認リクエストのタイミングも想定と違う。2回直して、3回目のプロンプトでようやく動いたとき、「ああ、これはカメラや ARKit だったらもっとひどいことになるだろうな」と感じました。
個人開発でアプリをリリースしてきた経験から言うと、Rork Max に何でも任せようとするのは危険です。ただ、何も任せないのは機会損失です。大事なのは「どこで迷わず任せられるか」と「どこで自分が介入するか」を事前に知っておくことだと思います。
ここではSwiftUI の 30 機能カテゴリを実際に試した結果を S・A・B・C の 4 段階で評価し、C 評価の機能を A 以上まで引き上げる具体的な手順をお伝えします。
評価基準と検証方法
まず、評価方法を明示します。各機能について同一の要件(「基本的な読み取り/書き込み/表示ができるコードを生成する」)を指示し、以下の5軸で採点しました。
コンパイル通過率 : 最初のプロンプトで Xcode ビルドが通るか
ランタイムエラー率 : 実機で動かして最初の1分でクラッシュしないか
コード品質 : エラーハンドリングが実装されているか、deprecated API を使っていないか
完全性 : 要件の主要部分が漏れなく実装されているか
必要プロンプト回数 : 使用に耐える品質になるまでに何回の指示が必要か
評価基準はこうです。
S評価 : 1〜2回のプロンプトで実用品質。コピペでそのまま使える水準
A評価 : 2〜4回のプロンプトで高品質に仕上がる。軽微な修正が必要
B評価 : 5〜8回のプロンプト、または大きめのコード修正が必要
C評価 : 10回超、または生成コードの大部分を書き直す必要がある
検証は実機(iPhone 16 Pro、iOS 18.4)と Xcode 16.3 で行っています。
実測ベンチマーク数値 — 30機能の集計
評価の背景にある数字も共有します。各機能カテゴリを同一条件で5回ずつ試行し、初回プロンプトでのコンパイル通過率、実用品質に達するまでの平均プロンプト回数、生成直後のコードを実機で1分動かしたときのクラッシュ率を記録しました。傾向を掴むための目安として捉えていただければ幸いです。
評価 代表機能 初回コンパイル通過率 平均プロンプト回数 実機クラッシュ率
S 基本UI・REST API・CRUD 約90% 1.4回 ほぼ0%
A StoreKit 2・MapKit・通知 約70% 2.8回 約5%
B Dynamic Island・HealthKit・カメラ 約40% 6.2回 約25%
C ARKit・Core ML・Metal 約15% 11回超 約60%
この数字を見て改めて感じるのは、S から C への落差が段階的ではなく、B と C の間で急に深くなることです。平均プロンプト回数は B までは一桁で収まりますが、C ではその概念自体が崩れます。10回を超えたあたりから、プロンプトを重ねて直すより、骨格だけ受け取って書き直す方が速くなる。これが後述する分担判断の根拠になっています。
初回コンパイル通過率にも目を向けていただければと思います。S・A では過半数が一発で通るのに対し、C では通る方が例外です。C 評価の機能は、生成物を叩き台ではなく参考実装の断片として扱うのが現実的だと感じています。
S評価 — 迷わず任せていい機能群
UI 構築・REST API・基本 CRUD はほぼ完璧に動きます。List、Form、NavigationStack、Sheet、Alert、TabView といった基本 UI コンポーネントは S 評価です。プロンプト1回目でコンパイルが通り、2回目でデザイン調整が完了するケースがほとんどです。
REST API との連携も S に入れています。URLSession ベースの非同期処理、Codable による JSON デコード、エラーハンドリング付きの fetch メソッド — これらは現在の Rork Max が最も得意としている領域です。
// Rork Max が生成した REST API 連携コード(S評価の例)
// エラーハンドリング・async/await・Codable がすべて揃っている
struct Article : Codable , Identifiable {
let id: Int
let title: String
let body: String
}
@MainActor
class ArticleViewModel : ObservableObject {
@Published var articles: [Article] = []
@Published var isLoading = false
@Published var errorMessage: String ?
func fetchArticles () async {
isLoading = true
errorMessage = nil
do {
guard let url = URL ( string : "https://jsonplaceholder.typicode.com/posts" ) else {
errorMessage = "Invalid URL"
isLoading = false
return
}
let (data, response) = try await URLSession.shared. data ( from : url)
guard let httpResponse = response as? HTTPURLResponse,
( 200 ... 299 ). contains (httpResponse.statusCode) else {
errorMessage = "Server error"
isLoading = false
return
}
articles = try JSONDecoder (). decode ([Article]. self , from : data)
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
このコードのポイントは @MainActor 付きで ObservableObject を定義し、isLoading と errorMessage を並走して管理している点です。生成された直後から、UI バインディングに対応したクリーンな設計になっています。
S 評価の全リストです。
基本 UI コンポーネント(List、Form、Sheet、Alert)
REST API × Codable 非同期取得
Supabase/Firebase Auth UI 基本フロー
CoreData 基本 CRUD(エンティティ1〜3つ程度)
UserDefaults/AppStorage
Swift Charts 基本グラフ(折れ線・棒・円)
TabView / NavigationStack 基本遷移
A評価 — プロンプト2〜4回で高品質に仕上がる機能群
StoreKit 2、MapKit、Core Location、Local Notifications は A 評価です。生成されるコードは概ね正しいのですが、特定の部分で調整が必要になります。
StoreKit 2 を例に取ります。基本的なサブスクリプション購入フローは動きます。ただし、購入復元(Transaction.currentEntitlements)と期限切れハンドリングが甘いことが多く、ここは明示的に指示する必要があります。
// A評価:StoreKit 2 の購入フロー(プロンプト3回で仕上げた版)
// ポイント:エラー種別の分岐とトランザクション検証を明示的に指定している
class SubscriptionManager : ObservableObject {
@Published var isSubscribed = false
private var updateListenerTask: Task< Void , Error > ?
init () {
// トランザクション更新の監視を開始
updateListenerTask = listenForTransactions ()
Task {
await updateSubscriptionStatus ()
}
}
deinit {
updateListenerTask ? . cancel ()
}
func purchase ( product : Product) async throws -> StoreKit.Transaction ? {
let result = try await product. purchase ()
switch result {
case . success ( let verification) :
let transaction = try checkVerified (verification)
await updateSubscriptionStatus ()
await transaction. finish ()
return transaction
case .userCancelled :
return nil
case .pending :
// ファミリー購入承認待ちなど
return nil
@unknown default:
return nil
}
}
private func checkVerified < T >( _ result: VerificationResult<T>) throws -> T {
switch result {
case . unverified ( _ , let error) :
throw error
case . verified ( let safe) :
return safe
}
}
@MainActor
func updateSubscriptionStatus () async {
for await result in StoreKit.Transaction.currentEntitlements {
if case . verified ( let transaction) = result {
if transaction.revocationDate == nil {
isSubscribed = true
return
}
}
}
isSubscribed = false
}
private func listenForTransactions () -> Task< Void , Error > {
return Task. detached {
for await result in StoreKit.Transaction.updates {
if case . verified ( let transaction) = result {
await self . updateSubscriptionStatus ()
await transaction. finish ()
}
}
}
}
}
最初のプロンプトで listenForTransactions() が省略されることが多く、アプリのバックグラウンド中に発生した更新を取りこぼします。「Transaction.updates のリスナーを必ず init で起動し、deinit でキャンセルすること」を明示的に指定すると、2回目以降で正しいコードが出てきます。
A 評価の全リストです。
StoreKit 2 基本課金フロー
MapKit 表示・ピン追加
Core Location 基本位置情報
Local Notifications(Expo Notifications 含む)
Keychain Services(基本的な保存・読み取り)
FaceID/TouchID 認証(LocalAuthentication)
SwiftUI アニメーション(基本・中程度)
AVFoundation 音声再生
B評価 — 詰まりやすいが攻略できる機能群
Dynamic Island、HealthKit、カメラ(AVFoundation 撮影)、WidgetKit が B 評価に入ります。生成されるコードは動くこともありますが、特定の組み合わせや状態遷移で問題が出やすく、指示の仕方を変えないと同じエラーが繰り返されます。
Dynamic Island(ActivityKit)の場合、最初の生成で ActivityKit の import が漏れることがほぼ確実です。また、ActivityAttributes の定義と ContentState の更新タイミングが混在したコードが出てくることがあります。
攻略のコツは、要件を細かく分割して送ることです。「Dynamic Island と Live Activities を実装して」という大きな指示は避け、以下のように段階的に送ります。
ステップ1:「ActivityAttributes プロトコルに準拠した DeliveryAttributes 構造体を
作成してください。ContentState には currentStatus: String と
estimatedTime: Date の2つのプロパティを含めてください」
ステップ2:「先ほどの DeliveryAttributes を使って Activity を開始する
startDeliveryTracking() メソッドを実装してください。
エラーハンドリングを含め、ActivityKit が利用可能かどうかの
チェックも忘れずに」
ステップ3:「Live Activities の UI(コンパクト表示・拡張表示・ロック画面表示)を
SwiftUI で実装してください。背景色は .blue、フォントは
.system(.headline) を基本にしてください」
このように分割すると、各ステップで高品質なコードが出てきます。一度に全部やらせると、どこかが抜けます。
HealthKit については、NSHealthShareUsageDescription と NSHealthUpdateUsageDescription のキー設定と、HKHealthStore の初期化タイミングについて明示的に指定する必要があります。以下の指定をプロンプトに含めると B から A に近い品質になります。
「HealthKit のアクセス要求は ViewDidAppear 相当のタイミングで行い、
アクセスが拒否された場合は設定アプリへの誘導を表示してください。
Info.plist に必要な Usage Description も追記してください」
B 評価の全リストです。
Dynamic Island / Live Activities(ActivityKit)
HealthKit(読み取り・書き込み)
AVFoundation カメラ撮影
WidgetKit(基本ウィジェット)
Background Task / BGTaskScheduler
Core NFC タグ読み取り
UNNotificationServiceExtension(リッチ通知)
C評価 — 人間の介入が必須な機能群
ARKit、Core ML カスタムモデル、Metal、CarPlay、App Clips はC評価です。生成されるコードに動作するものが含まれることもありますが、本番品質に仕上げるためには実質的に書き直しが必要です。
ARKit を例に挙げます。ARView と ARSCNView の使い分けが不安定で、同じプロンプトを複数回送っても異なる実装が出てくることがあります。また、ARSessionDelegate のエラー処理と ARWorldTrackingConfiguration の設定が簡略化されていることが多く、実機で動かすとすぐクラッシュします。
// C評価の典型例:Rork Maxが生成しがちな「動かないARKit」コード
// これをそのまま使うと実機でクラッシュする
class BadARViewController : UIViewController {
var arView = ARSCNView () // ❌ フレームバインドをStoryboard前提で設定していない
override func viewDidLoad () {
super . viewDidLoad ()
view. addSubview (arView) // ❌ オートレイアウトなし
arView.session. run ( ARWorldTrackingConfiguration ()) // ❌ SessionDelegateなし
}
}
// 実際に動作する最低限のARKit実装(人間が書き直した版)
class GoodARViewController : UIViewController , ARSCNViewDelegate , ARSessionDelegate {
private lazy var arView: ARSCNView = {
let view = ARSCNView ( frame : .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.delegate = self
view.session.delegate = self
view.autoenablesDefaultLighting = true
return view
}()
override func viewDidLoad () {
super . viewDidLoad ()
view. addSubview (arView)
NSLayoutConstraint. activate ([
arView.topAnchor. constraint ( equalTo : view.topAnchor),
arView.leadingAnchor. constraint ( equalTo : view.leadingAnchor),
arView.trailingAnchor. constraint ( equalTo : view.trailingAnchor),
arView.bottomAnchor. constraint ( equalTo : view.bottomAnchor)
])
}
override func viewWillAppear ( _ animated: Bool ) {
super . viewWillAppear (animated)
let config = ARWorldTrackingConfiguration ()
config.planeDetection = [.horizontal, .vertical]
if let formats = ARWorldTrackingConfiguration.supportedVideoFormats. first {
config.videoFormat = formats
}
arView.session. run (config, options : [.resetTracking, .removeExistingAnchors])
}
override func viewWillDisappear ( _ animated: Bool ) {
super . viewWillDisappear (animated)
arView.session. pause () // メモリリーク防止
}
func session ( _ session: ARSession, didFailWithError error: Error ) {
if let arError = error as? ARError {
switch arError.code {
case .cameraUnauthorized :
showCameraPermissionAlert ()
case .worldTrackingFailed :
restartSession ()
default:
print ( "AR error: \( arError. localizedDescription ) " )
}
}
}
private func showCameraPermissionAlert () {
let alert = UIAlertController (
title : "カメラへのアクセスが必要です" ,
message : "設定アプリからカメラを許可してください" ,
preferredStyle : .alert
)
alert. addAction ( UIAlertAction ( title : "設定を開く" , style : .default) { _ in
UIApplication.shared. open ( URL ( string : UIApplication.openSettingsURLString) ! )
})
alert. addAction ( UIAlertAction ( title : "キャンセル" , style : .cancel))
present (alert, animated : true )
}
private func restartSession () {
arView.session. run ( ARWorldTrackingConfiguration (),
options : [.resetTracking, .removeExistingAnchors])
}
}
C 評価の機能を使う場合、私のお勧めは「Rork Max に骨格だけ作らせて、コアのロジックは自分で書く」アプローチです。UI レイアウトや NavigationStack への組み込みは Rork Max が得意ですが、ARKit や Core ML の核となる処理は公式ドキュメントを参照しながら自分で実装する方が結果的に早いです。
C 評価の全リストです。
ARKit / RealityKit
Core ML カスタムモデル統合
Metal / カスタムシェーダー
CarPlay インテグレーション
App Clips
GameKit(マルチプレイヤー)
Passkeys(Authentication Services 最新)
実例 — C評価のオンデバイス Core ML 推論を A 品質まで引き上げる
C評価の機能でも、課題の分割と検証の当て方によっては実用品質に届きます。画像分類のオンデバイス推論を題材に、生成コードをどう直したかを記録しました。手元では最初のプロンプトでコンパイルが通らず、5回のやり取りで実機動作まで到達しています。
最初に「Core ML で画像分類する SwiftUI ビューを作って」と一息に頼むと、次のようなコードが返ってきました。モデルの読み込みを View の body 内で毎回行っており、推論のたびにメモリを圧迫します。
// 生成直後(C評価の典型)— body 内でモデルを毎回ロードしてしまう
struct BadClassifierView : View {
@State private var label = ""
var body: some View {
VStack {
Text (label)
Button ( "分類" ) {
// ❌ ボタンを押すたびにモデルを初期化している
let model = try! MyImageClassifier ( configuration : . init ())
// ❌ try! でクラッシュ経路をそのまま残している
}
}
}
}
ここから3段階に分けて指示を送り直しました。ステップ1では「VNCoreMLModel の初期化は View の外で一度だけ行い、シングルトンとして共有してください」と指定します。ステップ2では「推論は Vision の VNImageRequestHandler 経由で行い、確信度上位1件のラベルとスコアを返してください」と依頼します。ステップ3で「try! を使わず、モデル読み込み失敗と推論失敗をそれぞれ列挙型のエラーで表現してください」と念を押しました。
最終的に落ち着いたのが次の形です。モデルは一度だけ構築し、推論は非同期に逃がし、失敗経路が型で表現されています。
// 5回のプロンプトで到達したA品質のCore ML推論
enum ClassifierError : Error {
case modelUnavailable
case inferenceFailed
}
final class ImageClassifier {
static let shared = try? ImageClassifier ()
private let model: VNCoreMLModel
private init () throws {
guard let coreMLModel = try? MyImageClassifier ( configuration : MLModelConfiguration ()) else {
throw ClassifierError.modelUnavailable
}
self .model = try VNCoreMLModel ( for : coreMLModel.model)
}
func classify ( _ image: CGImage) async throws -> (label: String , confidence: Float ) {
try await withCheckedThrowingContinuation { continuation in
let request = VNCoreMLRequest ( model : model) { request, _ in
guard let results = request.results as? [VNClassificationObservation],
let top = results. first else {
continuation. resume ( throwing : ClassifierError.inferenceFailed)
return
}
continuation. resume ( returning : (top.identifier, top.confidence))
}
request.imageCropAndScaleOption = .centerCrop
let handler = VNImageRequestHandler ( cgImage : image, options : [ : ])
do {
try handler. perform ([request])
} catch {
continuation. resume ( throwing : ClassifierError.inferenceFailed)
}
}
}
}
生成物との差分は主に3点でした。モデル構築の位置、try! の排除、そして推論の非同期化です。いずれも Rork Max が初回に外しやすいところで、逆に言えばこの3点を最初のプロンプトに条件として書いておけば、C評価の機能でも初回品質が一段上がります。書き直しの負担を減らす鍵は、生成後に直すのではなく、生成前に地雷を名指ししておくことだと感じています。
よくある落とし穴と対処法
B 評価・C 評価の機能で繰り返し遭遇する落とし穴を 5 つまとめます。
① MainActor の扱いミス
Rork Max が生成するコードで最もよくあるのが @MainActor の付け忘れです。ObservableObject のサブクラスに @MainActor が付いていないと、バックグラウンドスレッドから @Published プロパティを更新しようとして Warning が出ます。
// 生成されがちなパターン(Warningが発生する)
class BadViewModel : ObservableObject {
@Published var items: [ String ] = []
func loadItems () async {
// バックグラウンド処理後に items を更新するとWarning
items = await fetchFromServer ()
}
}
// 正しいパターン
@MainActor
class GoodViewModel : ObservableObject {
@Published var items: [ String ] = []
func loadItems () async {
items = await fetchFromServer ()
}
}
プロンプトに「ObservableObject には必ず @MainActor を付けてください」と一文加えるだけで、この問題はほぼ解消します。
② @State vs @StateObject の混在
値型(struct)には @State、参照型(class)には @StateObject。この区別が曖昧なコードが生成されることがあります。特に自作した ViewModel が @ObservedObject で宣言されていると、View が再生成されるたびに ViewModel もリセットされます。
③ Preview Provider がクラッシュする
HealthKit や Camera など、実機でないと動作しない機能を Preview で表示しようとするとクラッシュします。生成コードの Preview 部分に Mock データを使うよう明示的に指定してください。
④ フレームワーク間の依存関係
Core ML と Vision Framework を同時に使う場合、import の順序とモデル初期化のタイミングが問題になることがあります。「Core ML の VNCoreMLModel 初期化は View 外で行い、シングルトンとして管理してください」と指定すると安定します。
⑤ Entitlements の設定漏れ
HealthKit、Push Notifications、NFC、iCloud 等は Xcode の Signing & Capabilities でケイパビリティを有効化しないと実行時エラーになります。Rork Max はコードは生成しますが、.entitlements ファイルの設定はしません。この設定は自分で行う必要があります。
評価ランク別に測った開発時間
個人開発では、品質と同じくらい「どれだけ時間を溶かすか」が判断材料になります。同じ小規模機能をゼロから実装したとき、実用品質へ到達するまでにかかった手元の時間を記録しました。
評価 実用品質までの目安時間 Rork Max に任せる範囲
S 15〜30分 ほぼ全部
A 30〜60分 骨格+大半のロジック
B 1.5〜3時間 骨格と定型部分のみ
C 半日〜1日 UI と周辺のみ
この時間差は、収益設計にそのまま効いてきます。私が個人開発で続けてきたのは、AdMob による広告とサブスクリプションで小さく回収するタイプのアプリです。Dolice 名義で公開してきたアプリの多くも、この型に収まります。この型なら主要機能が S・A に収まるため、1本あたりの実装時間を短く抑えられます。逆に AR やオンデバイス ML を売りにするアプリは、C評価の時間コストを最初から織り込んでおかないと、リリースが遠のきます。
何を作るかを決める前に、その機能がどの評価帯に属するかを見積もる。この一手間が、個人開発のペース配分を大きく左右すると考えています。回収の速い S・A 中心のアプリで足場を作り、余力で C 評価の挑戦的な機能に投資する。限られた時間で続けていくうえでは、その順序が現実的だと感じています。
判断フレームワーク — 機能別の最適な使い方
30 機能の評価データから導いた判断基準をまとめます。
Rork Max に完全に任せていい条件
アプリの主要機能が S・A 評価の機能で構成されているとき。具体的には「REST API でデータ取得して一覧表示し、サブスクリプションで収益化する」というパターンは、ほぼ Rork Max だけで完結します。このカテゴリのアプリなら、コードの 90% 以上を AI 生成に委ねて問題ありません。
Rork Max をスターターとして使い、自分でチューニングするケース
B 評価の機能を含むアプリ。Dynamic Island 対応、HealthKit 連携、WidgetKit など。Rork Max に骨格を作らせてから、評価方法で述べた「段階的プロンプト」アプローチで仕上げてください。開発時間の目安は S 評価の 2〜3 倍です。
C 評価機能を含むアプリを作りたいとき
ARKit や Core ML を使ったアプリは、Rork Max を「補助ツール」として使うのが現実的です。UI レイアウト、ナビゲーション、非 AR 部分のビジネスロジックは Rork Max に任せ、AR 処理と ML 推論のコアは自分で実装する分担が最も効率的です。
アプリ開発で感じるのは、Rork Max の能力が「確率的」であるということです。同じプロンプトでも毎回同じコードが出てくるわけではなく、品質にばらつきがあります。この記事の評価はあくまで傾向であり、うまくいく日とそうでない日があります。そのばらつきを受け入れながら、得意不得意を把握した上で使い倒すのが、個人開発者としての現実的なスタンスだと感じています。
もし ARKit や Core ML 系のアプリに本格的に挑戦したい場合は、Rork Max × ARKit・Core ML 実装メモ とSwiftUI 上級テクニックの実装メモ も合わせて参照してみてください。どちらも Rork Max での実装に特化した内容です。
Rork Max の評価は現在も進行形です。新バージョンのリリースごとに生成品質が変わっていくので、この記事の内容も定期的に更新していきます。最後まで読んでいただき、ありがとうございました。