Repository navigation
Description
On the iPhone Duo cover display, the control column gives the window a one-sided safe area: 84 pt on the right and 0 on the left, in a 466 x 678 pt window. Apps that want content centered on the display have to mirror that inset on the left, and today they can only do it from JS with useSafeAreaInsets().
That is too late during a fold or unfold. iOS keeps showing the first frame committed for the new display through the transition, and JS is busy at that moment. In our app, JS insets reached the new display about a second after its first frame, so content appeared in the wrong place and then shifted, by 35 pt on one measured screen. Since #803, SafeAreaView applies native insets in time, but it has no way to express a mirrored inset.
Proposal
Add a way for SafeAreaView to apply the wider horizontal inset on both sides, resolved natively in the same layout pass.
We currently patch this in as a mirrored value for the left and right edge modes:
<SafeAreaView
edges={{ top: 'additive', left: 'mirrored', right: 'mirrored' }}
style={{ paddingHorizontal: 16 }}
/>--- a/common/cpp/react/renderer/components/safeareacontext/RNCSafeAreaViewShadowNode.cpp
+++ b/common/cpp/react/renderer/components/safeareacontext/RNCSafeAreaViewShadowNode.cpp
- left = Style::Length::points(getEdgeValue(
- edges.left, stateData.insets.left, left.value().unwrapOrDefault(0)));
- right = Style::Length::points(getEdgeValue(
- edges.right, stateData.insets.right, right.value().unwrapOrDefault(0)));
+ auto narrowerInset = fmin(stateData.insets.left, stateData.insets.right);
+ auto widerInset = fmax(stateData.insets.left, stateData.insets.right);
+ left = Style::Length::points(
+ edges.left == "mirrored"
+ ? fmax(widerInset - left.value().unwrapOrDefault(0), narrowerInset)
+ : getEdgeValue(
+ edges.left,
+ stateData.insets.left,
+ left.value().unwrapOrDefault(0)));
+ right = Style::Length::points(
+ edges.right == "mirrored"
+ ? fmax(widerInset - right.value().unwrapOrDefault(0), narrowerInset)
+ : getEdgeValue(
+ edges.right,
+ stateData.insets.right,
+ right.value().unwrapOrDefault(0)));Each edge gets the wider inset less its style padding, but never less than the narrower inset. The style padding is treated as padding the content keeps inside, so with insets of 0 and 84 and a padding of 16, both edges resolve to 68. With equal insets of 0, as on an ordinary portrait iPhone, both resolve to 0.
Evidence
Measured with this patch on 5.11.1 and React Native 0.88.0, on the iPhone Duo simulator in Xcode 27.2. We ran 18 opens and 18 closes across three screens and compared every recorded frame with the final one. None differed visibly in layout: the worst was 536 of about 145,000 compared pixels, with the status bar area excluded.
Limits of this evidence:
- We have not run it on a physical iPhone Duo.
- We have not run it on an ordinary iPhone.
- It is iOS only. Android's
SafeAreaViewEdgeModes.valueOfrejectsmirrored, so a real implementation needs an Android side. - We don't have a public repro yet, but can make one if that helps.
Question
Would you accept this, and in what shape? The style padding behavior above is what our layout needed, but it differs from the other edge modes, so a separate prop or different semantics may fit the library better. If the direction works for you, we can send a PR.
Activity
kockar96 commentedon Nov 5, 2026
Closing this. Further measurement shows that mirroring the inset in JS behaves the same, because the insets are identical on both displays of the device I was testing. The shift I described did not come from this. Sorry for the noise.