← 输入框|github.com|刷新
Skip to content

fix(ios): safe area layout when page view is offset in its superview - #11517

Draft
edusperoni wants to merge 1 commit into
mainfrom
fix/ios-safe-area-offset-frame
Draft

edusperoni wants to merge 1 commit into
mainfrom
fix/ios-safe-area-offset-frame

Conversation

@edusperoni

Copy link
Copy Markdown
Contributor

PR Checklist

What is the current behavior?

When a page's controller view does not start at (0,0) in its superview, safe-area layout breaks on iOS. The common trigger is page.ios.edgesForExtendedLayout = UIRectEdge.Bottom with a visible navigation bar: UIKit places the controller view below the bar (y ≈ 152pt with a large title).

Two places mix the controller view's frame (superview coordinates) with values in the view's own coordinates:

  1. Page.onLayout gets the native frame from View.layout() and lays out its content to bottom - insets.bottom / right - insets.right. The content ends up taller than the page by the frame offset.
  2. IOSHelper.getAvailableSpaceFromParent returns fullscreen = viewControllerView.frame. expandBeyondSafeArea compares it with safeAreaLayoutGuide.layoutFrame and the in-window position, which are both relative to the controller view, so the bottom check (inWindow.bottom >= safeArea.bottom + fullscreen.top) never passes.

What you see:

  • A view with iosOverflowSafeArea = true at the bottom of the page stops at the safe-area bottom instead of reaching the screen edge (under the home indicator).
  • A native UIToolbar at the bottom shows a gap below it. UIKit only extends a bar's background into the bottom safe area when the bar touches its superview's safe-area bottom, and the page content is the wrong size and position for that.
  • A page with edgesForExtendedLayout = Bottom inside a TabView gets its content pushed down by the offset, so the bottom rows end up under the tab bar.

What is the new behavior?

  • Page.onLayout subtracts the origin: childRight = right - left - insets.right, childBottom = bottom - top - insets.bottom.
  • getAvailableSpaceFromParent uses viewControllerView.bounds, so fullscreen, safeArea and inWindow share one coordinate space.

When the frame origin is (0,0), which is the default edgesForExtendedLayout = All case, both changes do nothing.

Tests

  • New apps/automated test SAFEAREALAYOUT.test_overflow_reaches_bottom_when_page_view_is_offset: a page with a visible ActionBar and edgesForExtendedLayout = Bottom, holding a GridLayout with rows *, 44 and an iosOverflowSafeArea Label in the bottom row. It checks that the page view is offset, then asserts that the page, grid and label all reach the screen bottom. On the iOS 26.3 simulator (iPhone 17 Pro) it fails without this change (GridLayout.bottom - actual:2970; expected: 2622) and passes with it.
  • Full apps/automated iOS suite on the same simulator: 1828 OK, 0 failed.
  • Manually tested in a separate repro app: a pushed page with a large-title navigation bar and a child view controller, a fullscreen modal, a TabView, and a plain page with default edges. Each was run with an overflow label and with a native UIToolbar at the bottom. Layouts with default edges are unchanged.

Follow-ups (not in this PR)

  • expandBeyondSafeArea still adds fullscreenPosition.left / fullscreenPosition.top in the right/bottom checks. Both are now always 0 on the controller-view and scroll-view paths, so the terms could be dropped.
  • IOSHelper.layoutView lays out the owner with controller.view.frame when the controller has child view controllers. This has no effect today because View.layout() reads the native frame again, but bounds would be consistent with this change.
  • A page with edgesForExtendedLayout = Bottom inside a TabView now lays out correctly at the bottom, but still shows an empty band of roughly 60pt under the navigation bar. It comes from the page reporting a top safe-area inset, set by the additionalSafeAreaInsets logic in UILayoutViewController.viewDidLayoutSubviews. This was already the case before this change and needs separate investigation.
  • Not tested yet: iPad/landscape (left/right insets), sheet-style modals.

Page.onLayout received the native frame in superview coordinates but laid
out its content as if it started at the origin, making the content taller
than the page by the frame offset. IOSHelper.getAvailableSpaceFromParent
compared the controller view's frame (superview coordinates) with safe
area and in-window positions in the controller view's own coordinates, so
iosOverflowSafeArea views never expanded to the bottom edge.

This shows up when the page view does not start at (0,0), e.g. with
edgesForExtendedLayout = UIRectEdge.Bottom under a visible navigation bar.
@nx-cloud

nx-cloud Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 6cdad60

Command Status Duration Result
nx run-many -t build -p core,webpack5,vite ✅ Succeeded 20s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-10-10 20:10:35 UTC

@pkg-pr-new

pkg-pr-new Bot commented Oct 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@nativescript/core@11517
npm i https://pkg.pr.new/@nativescript/vite@11517
npm i https://pkg.pr.new/@nativescript/webpack@11517

commit: 6cdad60

modos189 added a commit to IITC-CE/IITC-Prime that referenced this pull request Oct 22, 2026
Backports NativeScript/NativeScript#11517: for a Page, nativeViewProtected is the
controller's view, which a transition animates by transform, so its frame reads as
an off-screen rect while the safe-area values beside it are in bounds space.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant