The day after last September's release, the tone in my review section shifted. Nobody wrote "it crashes." They wrote "the title overlaps the status bar" and "the bottom button is hard to tap."
Not a line of my code had changed. What changed was the phone in their hand.
When you run a handful of apps as an indie developer, this stretch of the year is quietly unsettling. For the past few years I've kept one spare iPhone set aside as a beta device, so I can walk through my own apps before the final build reaches everyone. No Mac, no Xcode — just an old phone and half an hour.
Count the Time You Actually Have Left
Developer beta 7 for iOS 27 shipped on August 24. Beta 6 landed on August 17, so the cadence has been roughly weekly. The public release is scheduled for September.
The part worth thinking about is the order in which changes arrive.
Once the final version goes out, user devices flip to the new OS within days. Your fix, meanwhile, has to be written, built, reviewed, and approved before it lands. Their environment changes first, and yours always trails behind. The only way to shrink that gap is to look early.
There's a second ordering constraint. An app built against a beta SDK can go out through TestFlight, but it cannot be submitted for review. I wrote that one up separately in an app built with a beta SDK ships to TestFlight but not to review.
So of the three steps — check on iOS 27, fix, ship — only the last one stays closed until the final release. What you can do right now is the checking and the preparing. That still beats hunting for the cause in September with reviews piling up.
Set One Spare Device Aside
Do not put the beta on your main phone.
Rolling back from a beta means erasing the device and restoring it. A backup taken on the newer iOS cannot be restored onto the older one. Put the beta on your daily phone and you are effectively stuck there until the public release. That is not a risk worth taking on the device you use for messages and payments.
Setting up the spare takes three steps.
- Sign into the spare iPhone with your Apple ID and take a backup
- Go to Settings → General → Software Update → Beta Updates and select iOS 27
- Install your own published app from the App Store, exactly the way a user would
Step 3 is the one people skip. Not a development build, not a Rork Companion preview — the same shipped binary your users are running right now.
A development build carries slightly different assets and slightly different flags. "It didn't happen on my machine, but the review says it did" usually traces back to exactly that gap. I learned this the annoying way, and now I always install the store build.
An old phone from a drawer is fine. No SIM needed. Wi-Fi and an App Store sign-in are enough.
Walk the Screens in Order of Fragility
Once the device is ready, don't wander. Work down a fixed list, ordered so that the widest-impact, cheapest-to-fix items come first.
| # | What to check | What tends to break | Fix layer |
|---|---|---|---|
| 1 | Top and bottom safe areas | Headings collide with the status bar; bottom buttons sit under the home indicator | JS |
| 2 | Permission dialogs | Purpose strings wrap differently; prompts fire at a different moment | Native config |
| 3 | Photo picker and share sheet | The app doesn't regain focus after selection; sheet detents change height | JS + libraries |
| 4 | Notification appearance | Titles truncate; attached images don't render | Server / JS |
| 5 | WebView and external browser handoff | Back gestures fight with in-page swipes | JS |
| 6 | Largest accessibility text size | Button labels wrap to two lines and overflow their frame | JS |
In practice the first three surface most of what you'll find. The lower rows are rarer, but they're also the ones that sit unreported for months.
The reason for fixing the order in advance isn't really speed. It's that a fixed order lets you sweep every app against the same question. Running several wallpaper apps, I don't have the patience to go through them one at a time, screen by screen. Checking item 1 across all of them, then item 2, keeps the judgment consistent.
What You Can Harden From JavaScript Today
Three things don't need the beta device at all. All of them sit just past the patterns that Rork's generated code tends to produce.
Read the bottom inset as a number, not a wrapper
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
import { View, Pressable, Text, StyleSheet } from 'react-native';
function Footer() {
const insets = useSafeAreaInsets();
return (
<View style={[styles.footer, { paddingBottom: insets.bottom + 12 }]}>
<Pressable style={styles.button}>
<Text style={styles.label}>Save</Text>
</Pressable>
</View>
);
}
export default function App() {
return (
<SafeAreaProvider>
<Footer />
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
footer: { paddingHorizontal: 16, paddingTop: 12 },
button: { minHeight: 44, alignItems: 'center', justifyContent: 'center' },
label: { fontSize: 17 },
});SafeAreaView applies the device's safe area straight through as padding. Convenient — until you want a little extra breathing room at the bottom, at which point you end up with either doubled padding or none.
useSafeAreaInsets hands you the same measurement as a number. A number can be composed: insets.bottom + 12 expresses your spacing and the system's together. When the OS changes how it treats the home indicator, only the value of insets.bottom moves, and your layout arithmetic survives intact. Whether that measurement lives as a number or a wrapper is, in my experience, what decides how much work September costs.
Cap font scaling only where the frame is fixed
<Text
style={{ fontSize: 15 }}
maxFontSizeMultiplier={1.4}
numberOfLines={1}
ellipsizeMode="tail"
>
Save settings
</Text>Reaching for allowFontScaling={false} across the whole app is the wrong instinct. Someone chose a larger text size because they need it, and turning that off for your own convenience is not a trade you should make.
Instead, cap the multiplier only on elements whose height is fixed — button labels, tab bar items, badges. Let body copy scale freely, and protect just the spots where overflow makes the app unusable.
Version checks mean different things per platform
import { Platform } from 'react-native';
// On iOS, Platform.Version is a version string such as "27.0"
// On Android, Platform.Version is the numeric API level (36, for example)
const iosMajor =
Platform.OS === 'ios' ? parseInt(String(Platform.Version), 10) : 0;
export const isIOS27OrLater = iosMajor >= 27;Platform.Version returns an OS version string on iOS and an API level number on Android. The two values don't just differ in type — they describe different things.
A bare Platform.Version >= 27 therefore means "iOS 27 or later" on one platform and "API level 27 or later," which is Android 8.1, on the other. It runs without complaint, which is exactly why it's easy to miss on a later read. Splitting on Platform.OS first and naming the result pins the meaning in place.
One caveat: a new OS is not a reason to start adding branches. Run without them, find the places where behavior actually changed, and branch only there. Branches written in advance almost always turn out to have been unnecessary.
What You Can Ship, and What Has to Wait
When something does break, start by checking for dependency drift.
npx expo install --check
npx expo-doctorexpo install --check compares your installed packages against what the current SDK expects and offers the correct versions. expo-doctor goes further, catching config and native-side mismatches. Clearing both before blaming the OS makes the rest of the investigation much shorter.
From there, fixes split into two delivery paths:
- JavaScript-layer changes — layout, font scaling, conditionals, copy — ship through EAS Update. No review queue, no waiting
- Native config changes — purpose strings, Info.plist entries, new native modules — need a build and a review pass, which means after the public release
Knowing which side a fix falls on sets your September order of operations: push everything JavaScript can reach first, then batch the native changes into a single submission. Sending two or three separate builds through review costs you several days each time.
One Thing to Do Today
Pick an old iPhone out of the drawer and take a backup of it. That's genuinely enough for today.
Installing the beta can wait, and walking the screens can happen this weekend. With the setup done, the morning the public release lands you get to check your own apps before you open your reviews.
If you find clipped layouts once you're on the beta, the symptom-level fixes live in how to fix Rork app layouts clipped by the notch and home indicator.
I go through this same routine every year, and every year something new turns up. Thanks for reading.