RORK LABJP
TEST — The Rork Companion app lets you test on a real iPhone without a paid Apple Developer accountCLOUD — Code compiles on a cloud Mac, streaming a 60fps live simulator with real touch inputBROWSER — Design, code, and test entirely in Chrome or Safari — no Xcode requiredPUBLISH — Two-click App Store publishing keeps the submission process simpleMAX — Rork Max builds native Swift apps for iPhone, iPad, Apple Watch, and Vision ProRN — Standard Rork generates iOS and Android apps together with React Native (Expo)TEST — The Rork Companion app lets you test on a real iPhone without a paid Apple Developer accountCLOUD — Code compiles on a cloud Mac, streaming a 60fps live simulator with real touch inputBROWSER — Design, code, and test entirely in Chrome or Safari — no Xcode requiredPUBLISH — Two-click App Store publishing keeps the submission process simpleMAX — Rork Max builds native Swift apps for iPhone, iPad, Apple Watch, and Vision ProRN — Standard Rork generates iOS and Android apps together with React Native (Expo)
Articles/AI Models
AI Models/2026-05-02Advanced

Benchmarking Rork Max SwiftUI Native Generation Across 30 Features — What to Delegate and When to Step In

A systematic evaluation of Rork Max's SwiftUI AI generation capabilities across 30 feature categories, rated S through C. Includes practical prompt patterns and code fixes to elevate C-rated features to production quality.

Rork Max189SwiftUI55AI generation2benchmarknative development2quality evaluationprompt engineering2

When I started building a HealthKit-powered fitness tracker with Rork Max, the first prompt produced a working step count graph. I thought, "this is going to be smooth." Then I tried adding sleep data. The generated HKSleepAnalysis handling was subtly off — authorization timing was wrong, and the data parsing didn't account for sleep stage changes. Two revisions in, three prompts deep, I finally had something that worked. And I thought: if HealthKit takes this much effort, ARKit would be a serious challenge.

After years of shipping apps independently, my view is this: trying to delegate everything to Rork Max is risky. But delegating nothing wastes the most powerful development tool available today. What matters is knowing in advance which features you can hand off confidently — and which ones require you to stay involved.

This article presents a systematic evaluation of 30 SwiftUI feature categories based on real testing, rated S through C, along with specific techniques to raise C-rated features to production quality.

Evaluation Methodology

To keep comparisons fair, I used the same baseline requirement for each feature: "Generate code that performs basic read/write/display functionality for this API." I scored each category on five dimensions:

  • Compile pass rate: Does the first generated code build in Xcode without errors?
  • Runtime stability: Does the app survive the first minute on a real device without crashing?
  • Code quality: Is error handling present? Are any deprecated APIs used?
  • Completeness: Are the main requirements implemented without obvious gaps?
  • Prompts needed: How many iterations are required to reach usable quality?

Rating thresholds:

  • S: 1–2 prompts to reach production-ready quality. Copy-paste usable.
  • A: 2–4 prompts to reach high quality. Minor fixes needed.
  • B: 5–8 prompts, or substantial manual code changes required.
  • C: 10+ prompts, or most of the generated code needs to be rewritten.

All testing was done on an iPhone 16 Pro running iOS 18.4 with Xcode 16.3.

S-Rated Features — Delegate Without Hesitation

Basic UI, REST API, and core CRUD operations consistently earn S ratings. List, Form, NavigationStack, Sheet, Alert, and TabView all compile correctly on the first attempt and reach design-polished quality by the second prompt.

REST API integration is where Rork Max shines most consistently. URLSession-based async/await, Codable JSON decoding, and error-handling fetch methods — this is the category where AI generation is most reliable.

// S-rated: REST API integration generated by Rork Max
// All three pillars present: error handling, 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
    }
}

The @MainActor annotation on the class and the parallel management of isLoading and errorMessage are both present from the first generation. This is already designed for clean UI binding without any manual intervention.

Full S-rated category list:

  • Core UI components (List, Form, Sheet, Alert)
  • REST API with Codable async fetching
  • Supabase/Firebase Auth UI basic flows
  • CoreData basic CRUD (1–3 entities)
  • UserDefaults/AppStorage
  • Swift Charts (line, bar, pie)
  • TabView/NavigationStack basic transitions

A-Rated Features — High Quality After 2–4 Prompts

StoreKit 2, MapKit, Core Location, and Local Notifications land in the A tier. The generated code is largely correct, but specific parts require targeted adjustments.

StoreKit 2 illustrates this well. The basic subscription purchase flow works. But Transaction.currentEntitlements for purchase restoration and expiration handling tend to be oversimplified. You need to explicitly request complete behavior.

// A-rated: StoreKit 2 subscription flow (polished after 3 prompts)
// Key: explicit transaction verification and update listener lifecycle
 
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()
                }
            }
        }
    }
}

The listenForTransactions() call in init is frequently omitted in first-pass generation. Without it, any purchase events that happen while the app is backgrounded get lost. Adding the instruction "always start a Transaction.updates listener in init and cancel it in deinit" to your prompt resolves this reliably on the second attempt.

Full A-rated list:

  • StoreKit 2 purchase flows
  • MapKit display and pin annotation
  • Core Location basic geofencing
  • Local Notifications (including Expo Notifications)
  • Keychain Services (basic storage/retrieval)
  • FaceID/TouchID (LocalAuthentication)
  • SwiftUI animations (basic to intermediate)
  • AVFoundation audio playback

B-Rated Features — Workable With the Right Approach

Dynamic Island, HealthKit, AVFoundation camera capture, and WidgetKit are B-rated. The code sometimes works, but specific edge cases and state transitions cause problems — and repeating the same prompt often produces the same errors.

For Dynamic Island (ActivityKit), expect the ActivityKit import to be missing from the first generation almost every time. The ActivityAttributes definition and ContentState update timing also tend to get conflated.

The fix is to decompose the request into sequential steps rather than asking for everything at once:

Step 1: "Create a DeliveryAttributes struct conforming to ActivityAttributes.
         Its ContentState should have two properties: currentStatus: String
         and estimatedTime: Date."

Step 2: "Using the DeliveryAttributes from Step 1, implement a
         startDeliveryTracking() method that starts an Activity.
         Include error handling and a check for whether ActivityKit
         is available on this device."

Step 3: "Implement the Live Activity UI with compact, expanded, and
         lock screen presentations using SwiftUI. Use .blue as the
         background color and .system(.headline) as the base font."

Breaking it into three steps produces high-quality output at each stage. Asking for everything in one prompt results in something that compiles but behaves unpredictably at runtime.

For HealthKit, adding this instruction significantly improves output quality:

"Request HealthKit authorization when the view appears (equivalent to
viewDidAppear). If access is denied, show a prompt that guides the user
to the Settings app. Also add the required NSHealthShareUsageDescription
and NSHealthUpdateUsageDescription keys to Info.plist."

Full B-rated list:

  • Dynamic Island / Live Activities (ActivityKit)
  • HealthKit (read/write)
  • AVFoundation camera capture
  • WidgetKit (basic widgets)
  • Background Task / BGTaskScheduler
  • Core NFC tag reading
  • UNNotificationServiceExtension (rich push)

C-Rated Features — Human Intervention Required

ARKit, custom Core ML model integration, Metal, CarPlay, and App Clips earn C ratings. The generated code occasionally compiles, but getting it to production quality effectively requires a rewrite.

ARKit is the clearest example. The choice between ARView and ARSCNView is inconsistent — the same prompt can yield different implementations. ARSessionDelegate error handling is almost always absent, and ARWorldTrackingConfiguration settings are usually incomplete.

// C-rated: The ARKit code Rork Max typically generates
// This crashes on a real device within seconds
 
class BadARViewController: UIViewController {
    var arView = ARSCNView()  // No frame binding, no autolayout
 
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(arView)  // No constraints
        arView.session.run(ARWorldTrackingConfiguration())  // No delegate, no config
    }
}
 
// Minimum viable ARKit implementation (written by hand after C-rated output)
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()  // Prevent memory leak
    }
 
    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: "Camera Access Required",
            message: "Please enable camera access in Settings.",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { _ in
            UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
        })
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        present(alert, animated: true)
    }
 
    private func restartSession() {
        arView.session.run(ARWorldTrackingConfiguration(),
                           options: [.resetTracking, .removeExistingAnchors])
    }
}

For C-rated features, I recommend having Rork Max handle the non-core parts — UI layout, navigation, and unrelated business logic — while writing the AR/ML processing core yourself. This hybrid approach consistently produces better results than either full AI generation or fully manual implementation.

Full C-rated list:

  • ARKit / RealityKit
  • Core ML custom model integration
  • Metal / custom shaders
  • CarPlay integration
  • App Clips
  • GameKit (multiplayer)
  • Passkeys (latest Authentication Services)

Common Pitfalls and How to Fix Them

Five issues appear repeatedly when working with B and C-rated features.

① Missing @MainActor

The most frequent issue in generated ObservableObject subclasses is a missing @MainActor annotation. Without it, updating @Published properties from background threads produces runtime warnings and potential data races.

// Pattern that frequently appears without @MainActor (generates warnings)
class BadViewModel: ObservableObject {
    @Published var items: [String] = []
 
    func loadItems() async {
        items = await fetchFromServer()  // Warning: published from non-main thread
    }
}
 
// Correct pattern
@MainActor
class GoodViewModel: ObservableObject {
    @Published var items: [String] = []
 
    func loadItems() async {
        items = await fetchFromServer()  // Safe: MainActor ensures main thread
    }
}

Adding "always annotate ObservableObject subclasses with @MainActor" to your prompt eliminates this issue almost completely.

② @State vs @StateObject confusion

Value types (structs) use @State; reference types (classes) use @StateObject. When a custom ViewModel is declared with @ObservedObject instead of @StateObject, it gets reset every time the parent View re-renders — producing subtle bugs where data appears to reset randomly.

③ Preview Provider crashes

Features requiring real hardware — HealthKit, the camera, NFC — crash in Xcode Previews. Explicitly request that generated Previews use mock/stub data to avoid this.

④ Inter-framework dependency ordering

When combining Core ML with Vision Framework, model initialization timing can cause issues. Specifying "initialize the VNCoreMLModel outside the View, managed as a singleton" stabilizes the behavior significantly.

⑤ Missing Entitlements configuration

Rork Max generates the code for HealthKit, Push Notifications, NFC, and iCloud — but does not configure the .entitlements file or Xcode's Signing & Capabilities section. These must be set manually. Forgetting this step causes cryptic runtime errors that look like code bugs but are actually capability configuration issues.

Decision Framework — Matching Features to the Right Approach

Based on the 30-feature benchmark, here's how I approach the strategy decision for each new project.

Fully delegate to Rork Max when:

Your app's core features are drawn from S and A-rated categories. The pattern "fetch data from a REST API, display it in a list, monetize with subscriptions" is almost entirely achievable through AI generation. For apps in this category, 90% or more of the code can be AI-generated with confidence.

Use Rork Max as a starter, then tune manually:

Apps containing B-rated features like Dynamic Island, HealthKit, or WidgetKit. Let Rork Max generate the skeleton, then apply the step-by-step prompt approach described above to polish each feature. Budget roughly 2–3× the development time compared to S-rated-only apps.

Use Rork Max as scaffolding only:

Apps requiring C-rated features like ARKit or Core ML. Have Rork Max handle navigation, UI layout, and non-core business logic, while you implement the AR/ML processing core manually. This division of labor is consistently faster than either pure AI generation or fully manual development.

One thing I've come to accept is that Rork Max's generation quality is probabilistic. The same prompt doesn't always produce the same code, and quality varies across sessions. The ratings in this article describe tendencies, not guarantees. There are better days and harder days, and learning to work with that variability — rather than against it — is part of what makes the tool genuinely useful over time.

If you're exploring ARKit or Core ML more deeply, the Rork Max × Core ML guide and advanced SwiftUI API techniques guide go further into those specific areas.

The benchmark will need updating as Rork Max releases new versions — generation quality has improved noticeably with each major release, and the C-rated categories are gradually improving. Thank you for reading.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

AI Models2026-06-24
Receiving On-Device AI Output as Typed Data with Foundation Models Guided Generation
How to receive Foundation Models output as typed Swift structs instead of free text, with working code for Guided Generation and Tool Calling on-device.
AI Models2026-06-14
On-Device Image Tagging in Rork Max Swift Apps with Foundation Models Image Input
WWDC26 gave the on-device Foundation Models model image input. Here is how to add image tagging and captioning to a Rork Max Swift app entirely on-device, including the availability gate, structured output, and Vision interop.
AI Models2026-05-04
What Can Rork Max Actually Generate in SwiftUI? — Real-Device Testing in 2026
An honest assessment of Rork Max's SwiftUI native app generation — what it handles well, where it struggles, and what that means for your App Store submission. Based on real-device testing.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →