Repository navigation
feat: add corner-adapted safe area insets - #792
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Add a cornerAdaptation option to useSafeAreaInsets and a cornerAdaptation prop to SafeAreaView. Providers measure the vertical and horizontal corner-adapted insets natively with the regular insets, and initialWindowMetrics includes them. iOS 26+ uses the UIKit layout region API, Android 12+ approximates from the window's rounded corners, and other platforms return the regular insets. The default insets are unchanged.
janicduplessis
force-pushed
the
@janic/corner-adapted-insets
branch
from
October 21, 2026 04:23
8f96de3 to
9ed2236
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On the iPhone Duo cover display (iOS 27.1, folded), the status bar moves into a column on the right edge, so the window's safe area has
top = 0. The display still has rounded corners. A header laid out from the regular insets runs under the corners and its buttons and title get clipped. UIKit's own bars avoid this with corner adaptation: iOS 26 addededgeInsets(for: .safeArea(cornerAdaptation:)), which returns the safe area enlarged where the corners reach past it. On the folded Duo the window's vertical value hastop = 17.33.This PR exposes those corner-adapted insets as an opt-in. Defaults are unchanged:
useSafeAreaInsets(),SafeAreaViewandinitialWindowMetrics.insetsstill return the regular insets.Reviewer notes:
horizontalis not a no-op on regular iPhones: UIKit returnsleft 18, right 18on an iPhone 18 Pro, where the regular side insets are 0.compileSdkVersionfallback inandroid/build.gradlegoes from 30 to 31, forRoundedCorner.API details:
verticalenlarges the top and bottom insets,horizontalthe left and right. The names follow UIKit'scornerAdaptationlabel and.vertical/.horizontalaxes.initialWindowMetricscarryverticalCornerAdaptedInsetsandhorizontalCornerAdaptedInsets, so the first frame is synchronous.RoundedCorners; on iOS the regular insets, the frame in the window and the window frame. iOS layout passes always measure again, because UIKit updates the values while the Duo switches displays before any of those inputs change.SafeAreaCornerAdaptedInsetsContext, as{ vertical, horizontal }.SafeAreaInsetsContextis unchanged: still a plain insets object, so spreading it, mocks and hand-made providers keep working. Without the option the hook reads onlySafeAreaInsetsContext, so a change in only the corner-adapted values does not re-render plain consumers. With the option it reads the new context and falls back to the regular insets when it is missing, e.g. under a hand-madeSafeAreaInsetsContext.Provider.UIViewLayoutRegionAdaptivityAxisis a single choice of none, horizontal or vertical. Apps can combine the two results per edge.SafeAreaViewselects the variant natively and, like the regular insets, reads it from the nearest provider, so it matches the hook. Measuring on the view itself would not work well: UIKit's value depends on the receiver's height (below), so the inset would depend on the padding it produces.initialMetricswithout them.Platform behavior
UIView.edgeInsetsForLayoutRegion:withsafeAreaLayoutRegionWithCornerAdaptation:, guarded by the iOS/tvOS 26 SDK and@available(iOS 26.0, tvOS 26.0, visionOS 26.0, *), so the library still builds with older Xcode versions. Older iOS returns the regular insets.WindowInsets.getRoundedCorner()radius, in px, rounded to whole px. Below API 31 the variants equal the regular insets.UIKit's rule
I sampled
edgeInsets(for: .safeArea(cornerAdaptation:))on 206 views per device (strips of varying height and width at each edge, offsets from 0 to 70 pt, corner boxes, mixed offsets) on an iPhone 18 Pro (iOS 27.0), an iPad Pro 11-inch M5 (iOS 27.0) and the Duo folded and unfolded (iOS 27.1). This rule reproduces all 1,648 values on every device:The regular side inset doesn't factor in: on the folded Duo the top-right corner applies even though it sits in the 84 pt status bar column.
_displayCornerRadius)So
_displayCornerRadiusis not always the radius UIKit uses (iPad, unfolded Duo, the Duo hinge side). Android reads the per-corner radius fromgetRoundedCorner(), which plays that role. A Python port of the Kotlin implementation, fed the fitted radii, also matches all 1,648 samples on each device.Sample values (all match the rule)
Related: #321 asked for
getRoundedCornervalues on Android.Test Plan
Unit tests cover the hook option, the fallback when metrics have no variants, updates from the native event, nested providers, the
cornerAdaptationprop reaching the native view, and that a plain consumer does not re-render when only a variant changes.The example app has a new "Corner Adaptation" screen (dev menu): a header
SafeAreaViewwhosecornerAdaptationcycles throughvertical,horizontaland none, and the three inset sets from the hook andinitialWindowMetrics. It renders in a full screenModalbecause react-native-test-app hosts the app in aUINavigationController, whose safe area already reportstop 24on the Duo cover.verticalverticalverticalSimulator screenshots don't draw the display's corner mask, so the clipping itself isn't visible; the header moves down by the 17.33 pt inset.
t 0, r 84, b 34; verticalt 17.33; horizontall 2.33;initialWindowMetricshas the same variants. Unfolding updates the provider live to verticalt 16and horizontall 16, matching the UIKit probe.t 62, b 34; vertical equal; horizontall 18, r 18.t 62, b 34), the pre-iOS 26 fallback.cmd overlay fabricateon a rooted Google APIs image only accepts integer resource types, so it can't setandroid:dimen/rounded_corner_radius.rounded_corner_radiusis refused at install: an overlay targetingandroidmust be signed with the platform key or name an overlayable, and this resource isn't in one./product/overlayneeds a-writable-systememulator. It never finished booting on this heavily loaded machine (adb shell stayed unresponsive for over 40 minutes, twice).The Android numbers still need a check on hardware with rounded corners, e.g. any Pixel.
The device runs use the new architecture. The iOS Paper files are compiled in that build but not exercised; the Android
src/papersources were not compiled.