Apple’s upcoming iPhone Duo is set to release exactly one month from today, and it is easily the most radical shift in iOS hardware design since the original iPhone X. Featuring an outer cover screen and a gorgeous unfolding inner dual display, the Duo introduces a computing paradigm that mobile game developers have spent a decade ignoring: dynamic, runtime aspect ratio transformations.
Our flagship title, Whoops!!, was originally engineered as a classic portrait-locked mobile game atop our cross-platform engine, DurianCore. For years, the design assumption was simple: screens are tall, buttons scale as fractions of screen width, and the board sits snugly in the middle.
Taking Whoops from a fixed 9:16 portrait viewport to a responsive, dual-screen foldable experience turned out to be a masterclass in geometry gymnastics, UIKit/SwiftUI bridging, and dodging hardware edge cases. Here is the full story of how we built day-one Duo support—and the lessons every mobile developer should know before next month's launch.
1. The 6-Orientation Trap & Pure Aspect-Ratio Layout
The first shock when booting up the iPhone Duo simulator is that traditional device orientation (UIDeviceOrientation) becomes almost meaningless. With two screens that can each be held in portrait or landscape, the hardware effectively exposes six distinct physical postures.
More dangerously, an unfolded inner display in "portrait" is nearly a 3:4 rectangle—almost square—while unfolded "landscape" is 4:3. Conventional thresholds (like checking whether width > height * 0.65) falsely categorized the unfolded portrait screen as widescreen, stretching menus awkwardly across the display.
The Fix: We threw out accelerometer-based orientation checks and grounded the entire layout engine in a single, unshakeable truth: active usable aspect ratio.
public func isWidescreen() -> Bool {
return self.width > self.height // Simple, robust, immune to folding posture bugs
}
If width exceeds height, the engine boots into our dedicated side-by-side landscape gameplay layout. If height exceeds width, it treats the screen as portrait—whether on a standard iPhone or the unfolded Duo's giant canvas.
2. The Virtual Viewport: portWidth, portX, and portScale
Like many legacy 2D games, Whoops originally calculated UI sizes using direct fractions of screen width: a card was 0.4 * width, a button was 0.7 * width. On an unfolded 850pt-wide display, a 70% button ballooned into a colossal banner that pushed navigation buttons right off the canvas.
Rather than rewriting hundreds of individual UI elements across dozens of menu screens, we introduced the concept of a Simulated Portrait Viewport into DurianCore:
getPortraitWidth(): Calculates a comfortable mobile phone column width (clamped to min(width, height * 0.6)).
getPortraitXOffset(): Centers this virtual column in the middle of the display.
portScale(fraction): Scales any UI element proportionally against the simulated column rather than raw screen width.
On standard phones, portScale(0.4) evaluates to exactly 0.4 (100% backward compatible). On the Duo, title screens, lobby cards, and store menus center themselves naturally with elegant dark purple checkered letterbox margins, feeling deliberate rather than stretched.
3. Geometry Gymnastics: Dodging Display Corner Radii
Unlike previous iPhones with subtle bezel curves, the iPhone Duo features aggressive rounded display corners to accommodate its hinge and camera hardware. In our initial widescreen landscape build, our square game board sat at x = 0 against the left edge.
The result? The top-left and bottom-left tiles of the board were cleanly chopped off by the physical display corners!
To fix this, we queried the actual display corner radius via _displayCornerRadius and derived a trigonometric corner-safe inset:
$\text{Inset} = r - r \cdot \cos(45^\circ) \approx 0.293 \times r$
By offsetting the board by cornerSafeInset(), the square game board stays as large as physically possible while remaining 100% visible inside the safe display boundary.
4. The Floating Status Bar Saga: Why We Cut It
When testing folded outer-screen mode, we noticed a fascinating quirk: iOS attempted to display a vertical status bar along the right edge of the screen, covering up camera cutouts and uneven corners. Initially, we thought: "Hey, this turns the remaining game view into a neat rectangle! Let's keep it."
In practice? It was a disaster. The floating status bar overlay sat directly over in-game fast-forward buttons, shifted touch event coordinates, and created an awkward asymmetrical bezel that looked like an OS glitch.
We made the definitive call: on the iPhone Duo, the status bar is suppressed completely.
override open var prefersStatusBarHidden: Bool {
if isIPhoneDuo() { return true } // Full stop: clean canvas on the Duo
return isWidescreen()
}
With safe-area black bars and clean window bounds, the game looks like a polished, immersive native experience rather than an app fighting the OS.
5. The Dual Gameplay Layout: Alternating HUDs & Docked Ads
In landscape widescreen on tablets and the Duo, Whoops splits into two functional zones:
- Left Zone: The game board, maximizing square dimension and offset for rounded corners.
- Right Zone: A dedicated control and player panel.
To keep the right panel visually dynamic, we packed the 4 player HUD bars near the top and added alternating mirroring (even players show avatar on left, odd players on right). Fast-forward and menu buttons float safely in the top-right.
Below the HUD bars sits a dedicated AdMob banner. Because Google Mobile Ads anchored adaptive banners require the requested AdSize width to match the SwiftUI container frame, we compute adaptive widths specifically for the right-hand column:
let adWidth = wide ? min(usableWidth * 0.28, 320.0) : usableWidth
let adSize = getAdBannerSize(size: CGSize(width: adWidth, height: usableHeight))
This guarantees crisp, zero-clipping ad rendering in both portrait and widescreen.
6. Surviving the Fold: Clean Engine Reboots
What happens when a player starts a turn on the outer folded screen and unfolds the device mid-game? On Android, the OS traditionally destroys and recreates the Activity. On iOS, SwiftUI’s UIViewControllerRepresentable merely calls updateUIViewController.
If you only animate elements to new coordinates, orphaned views, old subscreen dimmers, and card touch-targets linger in the UIKit view hierarchy. If you were in a lobby, coordinate systems criss-crossed.
Our solution was to implement an atomic clean engine reboot:
- When a fold/unfold or significant resize (>10% dimension change) is detected:
- All subviews are purged from the root controller:
self.view.subviews.forEach { $0.removeFromSuperview() }.
- If in gameplay, the running game thread halts and seamlessly re-hydrates from the disk autosave (preserving exact turn, RNG seed, and deck state) into the new layout mode.
- If in menus, the controller reboots cleanly to the Title Screen.
Furthermore, for dramatic cutscenes like the Swap and Whoops!! smash animations—where pieces slide off-screen to swap places—we built dynamic theatrical black side curtains in widescreen. Instead of seeing pieces slide into the purple letterbox margin and blip away, they vanish behind solid black masks and slide back in with cinematic flair.
Ready for Day One
Building for cutting-edge hardware requires balancing pragmatic backwards compatibility with fresh UX thinking. By investing in our foundation—virtual viewports, aspect-ratio-driven state machines, and clean lifecycle transitions—Whoops!! is ready to deliver an extraordinary experience when players unfold their brand new iPhone Duos next month.
See you on the board!