◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/App Dev
◇ App Dev/2026-05-27Intermediate

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.

ios12iphone-airiphone-17screen-resolutionapp-dev

✦ Premium Article

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.

What 29 ternary branches actually look like

The old macros were uncomplicated.

#define IS_IPHONE_AIR \
  ([UIScreen mainScreen].bounds.size.width == 420.0f && \
   [UIScreen mainScreen].bounds.size.height == 912.0f)
 
#define IS_IPHONE_17_PRO \
  ([UIScreen mainScreen].bounds.size.width == 402.0f && \
   [UIScreen mainScreen].bounds.size.height == 874.0f)
 
#define IS_IPHONE_17_PRO_MAX \
  ([UIScreen mainScreen].bounds.size.width == 440.0f && \
   [UIScreen mainScreen].bounds.size.height == 956.0f)

The trouble lived in the macros that returned layout values. Take the navigation bar offset:

#define NAV_BAR_OFFSET \
  (IS_IPHONE_AIR ? 18.0f : \
   IS_IPHONE_17_PRO_MAX ? 22.0f : \
   IS_IPHONE_17_PRO ? 18.0f : \
   IS_IPHONE_16_PRO_MAX ? 20.0f : \
   IS_IPHONE_16_PRO ? 18.0f : 16.0f)

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.

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 $10 for lifetime access
View Membership →

Related Articles

◇ App Dev2026-10-15
My Ads Never Came Back After a Subscription Expired: Fixing the Direction of an Ad-Free Check
An ad-free flag stayed on after the subscription expired because the check pointed the wrong way. Here is how I rebuilt it to wait for two purchase queries, decide only from confirmed facts, and keep the last value on failure.
◇ App Dev2026-10-15
When adjustsFontSizeToFit Shrinks Just One Row, Suspect the Box Height, Not the String
Only the 10th row of a list drops to 4pt on iOS, though the data and strings are fine. I read the Yoga rounding and font-fit sources, then counted how often the box comes up short in Node.
◇ App Dev2026-10-15
When Only EAS Build Fails with "No matching variant", Check Your Local android/build First
Gradle passes on your machine, but the Android build on EAS stops at "No matching variant of project". Reading the reports on GitHub, I traced how a Windows-generated autolinking cache can reach the Linux builder, and wrote a small script that checks for it before you queue a build.