●TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13●OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude Code●SIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App Store●MAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessage●NATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core ML●SEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round●TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13●OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude Code●SIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App Store●MAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessage●NATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core ML●SEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Supporting iPhone Air and 17 Pro Max New Resolutions Across Six Apps in Parallel
How I added iPhone Air (420×912), 17 Pro (402×874), and 17 Pro Max (440×956) support across six apps in parallel — 29 ternary branches, a 6-app diff script, and a phased rollout that landed at zero resolution-related crashes in 14 days.
Apple shipped three new iPhone resolutions in a single fall: iPhone Air at 420×912, 17 Pro at 402×874, and 17 Pro Max at 440×956. As an indie developer who has been working in Objective-C and Swift on these apps since 2014, this was the most resolution-heavy year I can remember. My catalog — Beautiful HD Wallpapers, Ukiyo-e Wallpapers, Relaxing Healing, Law of Attraction, and two others — all share a common DefineManager.h shape that needed to be updated in parallel.
The legacy ternary-chain macros I'd built up over the years grew by 29 branches in this update, repeated across six apps. The order of those branches is the whole game: get one of them wrong and another device's layout shifts. Here's how I worked through it with Claude on Xcode and what I'd do differently next time.
It started with a single pixel under the safe area
The first sign that something was off came from Xcode 17's new simulators. Beautiful HD Wallpapers, with its AdMob banner pinned above the safe area, was leaving an 8-point sliver of background visible on the 17 Pro Max simulator. Not a crash, not a layout failure — just a thin band of incorrect color where my old offset assumed bounds.size.height == 932.0f.
iPhone Air, at 6.6 inches with Dynamic Island, demands respecting safeAreaInsets rather than reading UIScreen.main.bounds.size directly. Most of my code already did this. The problem was the few legacy macros that didn't.
// The old check that assumed iPhone 16 Pro Max was the tallest device#define IS_IPHONE_PRO_MAX \ ([UIScreen mainScreen].bounds.size.height == 932.0f)// 17 Pro Max ships at 956pt vertically, so the predicate flipped
The first internal build I pushed to TestFlight showed the same 8pt gap to every 17 Pro Max user. When you don't own the device — and as an indie developer I usually don't — your safety net is whatever TestFlight + phased rollout can catch in the first 24 hours.
I had 29 of these chains. Inserting three new branches into each, in the right order, while leaving the existing branches untouched, was the work. Air and 17 Pro happen to share an 18pt offset right now — I deliberately wrote them as separate branches anyway. The DRY savings would be one line; the future cost of merging them and then having to split them again would be much higher.
Twelve years of indie iOS work has taught me that "DRY today" and "easy to change tomorrow" are not the same thing. Six months from now I will have forgotten everything I'm not seeing on screen, so verbose code is a letter to my future self.
✦
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
✦Reproduce the checklist that took six apps from initial iPhone Air / 17 Pro / 17 Pro Max support to zero resolution-related crashes in 14 days, including 1%→10%→50%→100% phased rollout gates
✦Port the table-driven refactor I'm using to retire ternary-chain device detection in DefineManager.h before next year's resolution wave
✦Lift the Crashlytics device.modelIdentifier filter I rely on for monitoring 50M+ downloads across six wallpaper and wellness apps in one session
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.
Inside Xcode, the template I used with Claude on Xcode was deliberately conservative.
## Context- Target: DefineManager.h (shared macro definitions)- Existing macros: 16-device IS_IPHONE_* checks + 29 ternary-chain macros- New devices: iPhone Air (420×912) / 17 Pro (402×874) / 17 Pro Max (440×956)- Rules: - Do NOT reorder existing branches - Insert each new device branch in narrowest-screen-first order - Preserve indentation and inline comments on existing branches## Ask- Produce a patch adding the three new device branches to every one of the 29 chains- Each hunk must be ≤ 6 lines so it stays reviewable
"Do not reorder" and "preserve comments" are the two phrases that matter most for files like this. If you let the model regenerate the file fluently, six months later it becomes unreviewable.
Claude on Xcode returned 29 small hunks. I reviewed them in the Xcode sidebar one by one. Total time to apply the entire patch with reviews: about two hours.
Synchronizing six apps with defineManagerSync.sh
All six apps have their own DefineManager.h. I've tried both the "single source + patches" and "fully independent per app" approaches over the years, and I now keep them independent. Each app diverges in ad-gate logic and theme color enums, and shared headers tend to break in subtle ways the moment you do.
What I share is a tiny diff script. Right before each release I run:
#!/usr/bin/env bashset -euo pipefailapps=( "BeautifulWallpapers" "UkiyoeWallpapers" "RelaxingHealing" "LawOfAttraction" "FractalGalleria" "CoolWallpapers")REFERENCE="apps/BeautifulWallpapers/DefineManager.h"for app in "${apps[@]:1}"; do target="apps/${app}/DefineManager.h" if ! diff -q \ <(grep "IS_IPHONE_" "$REFERENCE" | sort) \ <(grep "IS_IPHONE_" "$target" | sort) > /dev/null; then echo "❌ ${app}: IS_IPHONE_* declarations diverge from ${REFERENCE}" diff -u "$REFERENCE" "$target" | head -40 exit 1 fidoneecho "✅ Device detection macros are in sync across all six apps"
This compares only the IS_IPHONE_* declarations. Layout values like NAV_BAR_OFFSET differ per app and shouldn't be normalized. The script caught one case where iPhone Air had been added to four apps but accidentally skipped on a fifth.
A 14-day phased rollout without owning the new devices
When you don't have hardware for the new resolutions, TestFlight plus App Store Connect phased rollout becomes your only real verification. I start every phased release at 1%. Across a portfolio that has shipped roughly 50 million downloads over the years, 1% is enough to land tens of thousands of users with the new devices within 24 hours.
My pinned Crashlytics filter for resolution-related issues:
app_version == "3.0.0"
AND device_model starts_with "iPhone18"
AND fatal == true
iPhone18 is the internal model prefix Apple uses for the 17 Pro, 17 Pro Max, and Air. One thing the docs don't tell you: depending on Crashlytics SDK version, the field can be reported under device.model or device.modelIdentifier, and I've been bitten by inconsistent results. I now keep both in the filter.
1% → 10% → 50% → 100% — the gates that got me to zero
The phased rollout schedule I now use, with the gate criterion at each step, looks like:
Day 1: 1% - 24h watch; resolution filter must be zero events
Day 2: 10% - 24h; ANR-free; no resolution complaints in App Store reviews
Day 4: 50% - 48h; if stable, re-enable cross-app push campaigns
Day 7: 100% - Full distribution; Crash-free users must hold ≥ 99.7%
Day 14: Retrospective — decide between keeping the 29 ternary branches as-is or migrating to a table-driven layout
Day 14 is the part of indie release management I most often see skipped. Without that retrospective, your DefineManager.h is one device generation away from being unmaintainable.
Where I'm going next — a table-driven layout
Anticipating that resolution counts will keep climbing, I'm in the middle of moving DefineManager.h to a Swift table-driven layout. Objective-C ternary chains were the right tool when I started this codebase in 2014. At sixteen devices and 29 chains, they're no longer paying their rent.
The shape I'm prototyping is a (modelIdentifier, key) -> CGFloat dictionary with a sensible default fallback for unknown models. I'm letting Claude on Xcode draft it as a Swift Package so each of the six apps can adopt it on its own schedule.
What I've learned in 12 years of indie work is that the right time to refactor isn't "before it breaks" — it's right after the last change that made the existing pattern feel obviously expensive. Having just added 29 branches, I have the cleanest possible picture of what should and shouldn't live in the table.
One thing to try before next year's resolution wave
If you ship any production iOS app, the most useful thing you can do today is launch your app on the iPhone Air, 17 Pro, and 17 Pro Max simulators in Xcode 17, and log both UIScreen.main.bounds.size and UIScreen.main.nativeBounds.size from your application(_:didFinishLaunchingWithOptions:). The logical and physical dimensions for each device are the foundation any future layout logic will sit on top of.
I hope this saves another indie developer a couple of late nights when the next round of resolutions ships. 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.