跪拜 Guibai
← All articles
Swift · iOS · SwiftUI

iPhone Duo Adaptation Field Notes: 8 Layout Pitfalls from a Shipping Photo App

By 茶底世界之下 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Any SwiftUI app being adapted for iPhone Duo will hit the same class of layout feedback loops, safe-area double subtraction, and toolbar identity regressions; the article shows how to diagnose and test each one.

Summary

Getting an existing iPhone app onto iPhone Duo means more than widening the canvas and moving panels. One photo editor author walked through every screen, from home to export, and found that each flow had its own spatial failure. Three-column catalogs overflowed the inspector, titles and sliders never quite aligned, and popovers squeezed the photo out of view. Some problems only showed up after a rotation: buttons retained correct enabled states but landed in wrong positions, and one portrait-to-landscape rotation triggered a persistent layout feedback loop.

The root cause of the hang was a circular dependency between navigation placement and inspector presence. With top navigation, the workspace solver had enough width for a tool rail, canvas, and 280pt inspector; switching to side navigation reduced available width so the inspector dropped out, which moved the navigation back to the top, reactivating the inspector. The fix made region selection re-use the same inspector feasibility check as structural selection, and added an eight-iteration regression test that cycles between the two navigation states.

Other fixes include reading actual slider track insets from UIKit, separating window size from content size so root resolution doesn't depend on navigation, treating safe areas in one layer only, and using system Sheets for detail popovers so they take the current external height. The author ends with a three-level verification strategy: pure value/strategy regressions, full integration build, and runtime checks of button positions, rotation continuity, and scrolling.

Takeaways
— The portrait-to-landscape hang happened because top navigation gave 867×553pt and fit a 56pt tool rail, 480pt canvas, and 280pt Inspector, while side navigation reduced the feasible width to 271pt, forcing the Inspector out and reverting navigation to top, which re-enabled the Inspector.
— The fix for the feedback loop makes region selection first pass the same Inspector feasibility check, then compare area only among feasible candidates.
— A regression test runs eight solver iterations, alternating between side and top navigation based on the previous Inspector state, asserting structure, canvas minimum width, Inspector width, and occlusion non-intersection each round.
— Outer and inner layers must not both subtract the safe area; one layer handles safe area and the inside uses the obtained content area directly, or the canvas shrinks an extra amount.
— The measured system adjacent spacing is 12pt; for a 16pt visual gap, the app adds 4pt on the relevant side, and leading/trailing must be handled separately for split-screen.
— Native UISlider container bounds differ from visible track endpoints; report actual track insets from UIKit and use them in SwiftUI, updating only when track width changes, not during value drags.
— Toolbar items need fixed identities and fixed placements with axisBehavior, so rotation switches item sets instead of letting buttons drift to the previous orientation's positions.
— For detail popovers, using a system Sheet with the external window's current available height fixes the case where rotating from portrait to landscape keeps the previous orientation's popover height.
— Verification should include direct landscape open, direct portrait open, landscape-to-portrait, portrait-to-landscape, and entry from both home and gallery.
— The occlusion-related offline test for the fixed layout passed 10 tests with 0 failures.
— The checklist includes checking that continuous layout inputs converge, grid width and paddings use one budget, and loading states preserve card identity and scroll position.
Conclusions

The layout feedback loop shows that navigation placement and feature density must be decided against the same constraint set; if the solver's choice changes navigation, and navigation changes the solver's input, you get oscillation.

Treating 'regular' width as iPad-only is a trap once a foldable expands a phone to regular dimensions; every branch that does this needs rechecking.

A one-layer safe-area contract works better than per-container inset subtraction on a device with dynamic division and occlusion regions.

Only testing a single width/height snapshot cannot catch layout instability; feeding consecutive states to the solver is the only way to verify convergence.

Catalog layout must be evaluated with the same content-width budget as the surrounding grid; swapping only the GridItem to flexible while previews stay fixed-width still overflows.

Concepts & terms
Reserved regions
On iPhone Duo, the system can reserve areas that divide or occlude content. `division` regions separate content (e.g., the hinge/boundary between screens), while `occlusion` regions partially obstruct it. Apps must read these from GeometryProxy's `reservedRegions(kind:)` and avoid placing interactive content under them.
Layout feedback loop
A cycle where layout A changes navigation, which changes layout input, which changes navigation back, repeating forever. In this app, choosing top navigation allowed a 280pt Inspector to fit, but switching to side navigation reduced the workspace width enough to eject the Inspector, which restored top navigation, re-allowing the Inspector.
axisBehavior
A SwiftUI toolbar API (iOS 27.1+) that declares whether a toolbar item prefers to live on the horizontal or vertical axis. It lets the same toolbar content adapt between top and side placements as the window rotates or changes shape.
Regular environment
A size class where an iPhone app gains iPad-like width and height. Once iPhone Duo fully unfolds, the app can enter a regular environment; code that treats `regular` as synonymous with iPad may need re-evaluation.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗