RORK LABJP
PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updatesPLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates
Articles/Dev Tools
Dev Tools/2026-04-24Advanced

Rork Max × Xcode — A Pro Workflow for Native Optimization After Generation

Taking a Rork Max SwiftUI project to shippable quality in Xcode, narrowed to three things: how to read Instruments, a regeneration branch layout that survives merges, and the privacy manifest that gets builds bounced.

Rork Max232Xcode2SwiftUI64Instruments2native optimization

Premium Article

There was a day I opened a freshly generated Rork Max project, ran it in the simulator, and archived it as-is. It passed review. Then I installed the build on a real device, scrolled a list, and watched it visibly stutter — a symptom the simulator had never once reproduced.

Speed of generation and shippable quality live on different axes. Now that Rork Max owns the first one, the second one takes up proportionally more of the day. That's the honest read after six months of running it on solo projects.

What follows is only that second half, in the order the work actually happens — with notes on where the time went.

Reading a Rork Max Project Structure

Before touching anything, understand what Rork generated.

MyApp/
├── MyApp.xcodeproj/
├── Sources/
│   ├── App/              ← application entry point
│   ├── Views/            ← SwiftUI views
│   ├── Models/           ← data models, Codable types
│   ├── Services/         ← API clients, persistence
│   └── Utils/            ← extensions, helpers
├── Resources/
│   ├── Assets.xcassets/
│   ├── Localizations/
│   └── Fonts/
└── .rork/                ← Rork's internal metadata (don't touch)

The cardinal rule: never edit .rork/. It's how Rork Max tracks what it generated so it can regenerate cleanly later. Hand-editing it breaks regeneration.

Here's the boundary in more detail:

LocationSafe to edit?Why
.rork/NoThe baseline for regeneration diffs. Editing it corrupts the next output
Sources/Views/Yes, but expect conflictsThe area Rork rewrites most. Keep your diffs minimal
Sources/Services/YesMeant to be replaced post-generation. Swap real API implementations here
Sources/Extensions/ (yours)PreferredA directory Rork never generates, so conflicts can't occur
*.xcodeprojYes, carefullyBuild Settings survive; target restructuring gets silently reverted

That last row matters most in practice. Build Settings flags persist across regenerations, but split a target or rework a scheme and the next generation may quietly undo it. Restructure the project file only once you're ready to stop regenerating entirely.

Three Instruments Profiles to Run

Generated code works. At the starting line, though, it stops at "functional." The evidence for pushing past that only comes from measuring on hardware.

The stutter I opened with never appeared in the simulator. Running on a Mac CPU, the expensive computation simply never surfaced as a cost.

1. Time Profiler — CPU Hotspots

First thing I check when something feels slow. The classic generated pattern that shows up as a hotspot:

// Slow: recomputes every frame during scrolling
var body: some View {
    List(items) { item in
        Row(item: item, score: computeScore(for: item))
    }
}

If computeScore is non-trivial, it recalculates on every frame while scrolling. The fix is a cached version:

@State private var cachedScores: [UUID: Double] = [:]
 
var body: some View {
    List(items) { item in
        Row(item: item, score: cachedScores[item.id] ?? 0)
    }
    .task {
        await computeAllScores()
    }
}

Profile a Release build on a device, always. An unoptimized Debug build reshapes the profile enough that functions which aren't bottlenecks float to the top. I once spent a full day optimizing a function that turned out to be irrelevant.

2. Allocations — Memory Usage

Image handling in generated code is often the biggest memory waste. Look at Persistent allocations (not Transient). Lists that use AsyncImage or UIImage(data:) without downsampling are a usual suspect.

A helper worth pasting in:

func downsample(image data: Data, to pointSize: CGSize, scale: CGFloat) -> UIImage? {
    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
    guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else {
        return nil
    }
    let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
    let downsampleOptions = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceShouldCacheImmediately: true,
        kCGImageSourceCreateThumbnailWithTransform: true,
        kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
    ] as CFDictionary
    guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
        return nil
    }
    return UIImage(cgImage: downsampledImage)
}

The kCGImageSourceShouldCache: false on the first line is load-bearing. Drop it and CGImageSource retains the decoded bitmap internally, roughly cancelling out the savings you get downstream. Every time someone tells me this helper "doesn't do anything," that line is missing.

3. SwiftUI Performance — Tracing a Redraw to Its Cause

SwiftUI's declarative model makes redraws non-obvious, and this is the area where Xcode 26 changed the tooling, so it's worth spending some time on.

Instruments 26 ships a dedicated SwiftUI template. Its Update Groups lane lays out, on a timeline, when SwiftUI is doing update work. Where Time Profiler answers "which function is expensive," this answers "which update held the main thread, and for how long."

The more useful half is the Cause & Effect Graph. Select an update, choose "Show Cause & Effect Graph," and you get a node graph showing how a state mutation propagated into body re-evaluation. The nodes to the left of a view are the events that triggered its update.

This lands directly on the pattern Rork-generated code produces most: a view observing an ObservableObject redraws for any property change, including unrelated ones. That used to be a guess-and-check exercise. Now the graph names the mutation responsible.

Once you know the cause, there are two ways to fix it:

// Before: observes the whole object — unrelated changes still redraw
struct ScoreBadge: View {
    @ObservedObject var store: SessionStore
    var body: some View { Text("\(store.score)") }
}
 
// After: takes only the value it needs; observation stays in the parent
struct ScoreBadge: View, Equatable {
    let score: Int
    var body: some View { Text("\(score)") }
    static func == (l: Self, r: Self) -> Bool { l.score == r.score }
}

Passing plain values and conforming to Equatable lets SwiftUI skip body re-evaluation when nothing changed. Rork Max tends to hand @ObservedObject down to leaf views, so converting just the high-frequency ones — list rows, mostly — measurably shrinks the time spent in Update Groups.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Which files in a Rork Max project you can safely edit, and which ones break regeneration when you touch them
Tracing a redraw back to the state mutation that caused it, using the SwiftUI Performance instrument in Xcode 26
A regeneration branch layout that preserves the merge base, plus how to avoid an ITMS-91053 rejection
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
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 $15 for lifetime access
View Membership →

Related Articles

Dev Tools2026-07-18
The Generated Screen That Quietly Jams at AX5 and in German — Putting Layout Resilience Checks on Rork Max SwiftUI
A record of running every Rork Max generated SwiftUI screen through pseudolocalization and the largest text size to find exactly where it jams. Covers when to reach for ViewThatFits, ScaledMetric and layoutPriority, plus the snapshot checks that catch regressions every time you regenerate.
Dev Tools2026-07-12
A symbolEffect Field Memo: Making Icons Move Nicely in Your Rork Max App
Animate SF Symbols in a Swift app generated by Rork Max using bounce, pulse, variableColor, and contentTransition, with working code, OS-version gating, and the mistakes I made from over-animating.
Dev Tools2026-07-09
Getting Rork Max's Swift Through Swift 6's Strict Concurrency Checking
A field record of taking a Rork Max-generated SwiftUI app through Swift 6 complete concurrency checking: 217 warnings cleared target by target, where @MainActor actually belongs, and measured before/after numbers.
📚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 →