← Back to blog

iPad-first SwiftUI without splitting your codebase

Most universal apps fail iPad users in the same way: they're an iPhone screen, but bigger. Lists pinned to the left wall of the screen, buttons sized for one finger, no consideration for the keyboard or pointer that ships with every modern iPad. SwiftUI makes the iPhone-stretched-into-iPad pattern very easy. It also makes the right pattern not much harder. Here's how.

The universal lie

When Apple says 'universal app,' marketing implies one binary serving both devices well. The reality is two distinct UX challenges that share an internal data layer.

On iPhone, you're working in a single column with a stack-based navigation. On iPad in landscape, you have at minimum two columns and a horizontal navigation. Pretending these are the same UX is the bug.

NavigationSplitView is the start

NavigationSplitView is the iPad-first primitive. On iPhone it falls back to NavigationStack automatically. The mental model: declare the columns you'd want on iPad, and SwiftUI handles the iPhone collapse for you.

The two-column form (sidebar + detail) is the right default for any list-based app. The three-column form (sidebar + content + detail) is the right default for any inbox-shaped app - mail, RSS, code review.

Size class breakpoints

Once you have the right top-level structure, refine layouts using @Environment(\.horizontalSizeClass). The pattern: use compact for iPhone, regular for iPad in landscape. iPad in portrait is still regular but feels narrower; design accordingly.

We use breakpoints sparingly. The instinct to special-case 'iPad' is usually wrong - design the regular-size-class layout to flow naturally, and iPad inherits.

Pointer + keyboard support

Every iPad in 2026 ships with full keyboard and pointer support implied. Treat it as a baseline:

  • Every primary action needs a keyboard shortcut. .keyboardShortcut("n", modifiers: .command) for new entry, .keyboardShortcut(\.cancelAction) for cancel.
  • Lists support .contextMenu for right-click on the pointer. Same menu, no extra code.
  • Hover states matter. SwiftUI's .hoverEffect() applies the platform-appropriate highlight automatically.
  • Don't auto-dismiss the keyboard on scroll if you're on iPad - the user is probably typing on a hardware keyboard.

Testing both at once

We run every screen in three configurations during dev: iPhone 15, iPad Pro 13" landscape, and iPad mini portrait. The mini portrait catches everything that 'works on iPad landscape' but cramps in narrow regular size class.

Xcode Previews lets you stack these:

#Preview("iPad Pro", traits: .landscapeLeft) { MyView() }
#Preview("iPad mini") { MyView() }

When to ship iPad-only features

Sometimes a feature only makes sense on the bigger screen - multi-pane editors, drag-and-drop assembly, big-canvas visualizations. Gate these on size class or device idiom in code, not in the binary.

Splitting your app into iPhone and iPad targets is almost always a mistake in 2026. One target, one codebase, two well-designed layouts.