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.