A few days ago I opened a small Rork-generated iPhone app on my iPad Air just to see how it would look, and the picture was rougher than I expected. The generated UI runs fine, but holding the device with one hand on iPhone and cradling it with two hands on iPad turns out to demand very different button positions and very different spacing. There is a wider gap between "it works" and "it feels usable" than I had remembered.
As an indie developer who has been shipping iOS apps since 2014, with a wallpaper app series that now totals over 50 million downloads, I have already retrofitted iPad support more than once. Over three weeks of poking at the Rork output, I noticed a few patterns worth writing down.
Three Things That Felt Off the Moment I Opened It on iPad
Even before I started measuring anything, three things felt obviously wrong.
First, the layout stretched edge to edge. Card components that sat comfortably on iPhone looked elongated on iPad, and the wallpaper thumbnails inside them looked cropped in a way that changed the feel of the app entirely.
Second, the touch targets felt crowded. Buttons sized for a one-handed thumb on iPhone began to overlap with their neighbors when I held the iPad with two hands and reached in. I mis-tapped the wrong button several times in the first ten minutes.
Third, the SafeArea assumptions were off. Rork's generated layout reserved space for the iPhone notch and home indicator, but on iPad those edges are much thinner, leaving awkward strips of empty space at the top and bottom.
The three weeks of work were essentially three weeks of untangling those three feelings, one at a time. I worked through them in this order: the skeleton of the screen (max width and container), then the rhythm of buttons and spacing, and finally the image pipeline and keyboard handling.
Reworking the Max Width and Container
The first thing I touched was the root layout: the maximum content width and the way the container is centered.
Rork generates layouts that assume iPhone, with flex: 1 stretching to fill the screen. On iPad that produces a sparse layout where information density falls off a cliff. I borrowed the approach I had landed on for my wallpaper apps years ago: constrain the content width and let the surrounding space breathe.
import { View, useWindowDimensions } from 'react-native';
function ContentContainer({ children }: { children: React.ReactNode }) {
const { width } = useWindowDimensions();
// Cap reading width once we hit iPad portrait (768pt and up).
const maxWidth = width >= 768 ? 720 : width;
return (
<View style={{ width: '100%', alignItems: 'center' }}>
<View style={{ width: '100%', maxWidth, paddingHorizontal: 20 }}>
{children}
</View>
</View>
);
}Dropping this ContentContainer at the root of every screen settled the iPad layout to a comfortable 720pt column with breathing room on either side. Most of the awkward card-stretching went away with it.
The detail that matters here is that I am branching on width rather than on the device type. That keeps Split View and Slide Over happy, and it means I do not have to revisit this code when a new form factor like Stage Manager (or a foldable iPhone) becomes more common. I personally use Split View to read documentation while testing, and a device-based branch would leave half-screen mode looking strangely empty.
Rebuilding the Sense of Touch Targets and Spacing
The next pass was about buttons and the empty space between them.
On iPhone, a tap height of 44 to 56pt is fine. On iPad, with the device cradled in two hands and thumbs reaching across the bezel, that same height made the boundaries between buttons hard to feel, and I kept hitting the wrong target. I settled on:
- Minimum primary button height: 52pt (iPhone) / 60pt (iPad)
- Minimum gap between buttons: 12pt (iPhone) / 20pt (iPad)
- Vertical gap between cards: 16pt (iPhone) / 24pt (iPad)
These numbers are tied to the shape of my own hands and the few devices I personally tested on, so they are an observation rather than a recommendation. Handing the iPad to family members usually produces slightly different numbers.
Implementation-wise, Rork's output uses StyleSheet.create directly. Rather than touch each generated component, I introduced a small hook that exposes spacing tokens.
// theme/spacing.ts
import { useWindowDimensions } from 'react-native';
export function useSpacing() {
const { width } = useWindowDimensions();
const isTablet = width >= 768;
return {
tapMin: isTablet ? 60 : 52,
gap: isTablet ? 20 : 12,
cardGap: isTablet ? 24 : 16,
};
}After this pass, my mis-tap rate on iPad dropped by roughly half. Because I only swapped the tokens and left the generated components alone, the adjustment survives even if I regenerate large portions of the screen from Rork later.
Habits Picked Up From Twelve Years of Wallpaper App Releases
Let me detour for a moment into something that shaped my sense of robust design.
Both of my grandfathers were temple carpenters. When I watched them as a child, they always read the grain of the wood before placing a nail, choosing where to apply force based on how the material wanted to bend. That sense of working with the grain rather than against it is still inside the way I write code.
Responsive code follows the same logic. Every extra device-specific branch is a place where the code gets a little more brittle. Branching on width or aspectRatio (the shape the device is in right now) instead of on Platform.isPad (the identity of the device) keeps the code honest about what it actually depends on. It also tends to age well when a new form factor shows up.
A small example for column counts:
function useColumnCount() {
const { width } = useWindowDimensions();
if (width >= 1024) return 4; // iPad landscape
if (width >= 768) return 3; // iPad portrait
if (width >= 480) return 2; // larger iPhone landscape
return 1;
}Across a wallpaper app series with over 50 million downloads, I have personally watched device-specific if statements multiply until maintenance stopped being fun. The habit I keep trying to leave in the code is the one I wish I had given my younger self.
There is a second habit I borrow from my grandfathers: after every fix, I check whether the codebase reads more honestly than before. If a change adds branches that make it harder for a human to follow the layout decision, I treat that as a nail driven against the grain, and I look for a better seam.
What I Am Still Adjusting After Week Three
Three weeks did not cover everything. A few threads are still open.
The first is image loading. Wallpaper thumbnails that looked fine at 1x on iPhone show their seams on the iPad Retina display, so I am rebuilding the loading flow with expo-image's transition and placeholder to smooth out the swap. On the server side I am also returning 1x, 2x, and 3x assets so the device can pick the right one.
The second is keyboard handling. iPad uses a translucent software keyboard, and with an external keyboard attached the software keyboard does not appear at all. That means KeyboardAvoidingView needs a second look. If you leave Rork's generated template alone, you can end up with a search field that stays hidden behind the keyboard on iPad. My wallpaper app only has a single search field, so I could patch the one spot, but apps with many forms would need a more systematic pass.
The third is testing. The simulator helps, but I have been borrowing time on my own iPad Air, my family's iPad mini, and an iPad Pro at the local Apple Store. The weakness of indie development is that my hands become the only reference, so getting other hands on the device matters more than I used to admit.
If I had to compress the three weeks into one sentence: Rork's output is solid, but moving it from "works on iPad" to "feels right on iPad" still requires a human pass with a real human hand. I am planning to open the same app in the Vision Pro simulator next, and I hope to write about that experience as well.
Thank you for reading. If you are retrofitting your own app for iPad, I hope these notes save you a small amount of time.