feat(ios): iPhone Duo support - #11438
Merged
Merged
Conversation
|
View your CI Pipeline Execution ↗ for commit 1c40591
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
commit: |
NathanWalker
marked this pull request as ready for review
October 2, 2026 19:35
NathanWalker
force-pushed
the
feat/iphone-duo-support
branch
from
October 2, 2026 19:35
da3a583 to
8a04a21
Compare
… screen Frame leaves positioning to UIKit, but for a Frame nested in a layout core is the container and never gives the navigation controller's view a frame. It keeps the UIScreen.main bounds UIKit creates it with, and its autoresizing mask then only tracks later changes of the container. That holds only while the scene matches the main screen. On iPhone Duo UIScreen.main is always the outer display, so an app launched on the inner display got an outer-display-sized Frame (466x678 in a 951x669 scene), which became 84x687 once the app moved to the outer display. Match the view to its container during layout when the Frame is hosted directly in a NativeScript view. Frames under a UIKit container (window root, TabView, SplitView, modals) are untouched.
MainScreen cached the first UIScreen it resolved. A window can move to a screen of another size, e.g. between the outer and inner displays of iPhone Duo, which left widthDIPs/heightDIPs (and the pixel sizes media queries are matched against) reporting the previous display. Resolve the bounds from the window's screen on each read. Scale keeps using the cached screen: it is read on hot paths and is the same on both displays, as layout-helper already assumes.
iOS 27.1 can present bars vertically (iPhone Duo), where fewer items fit and the rest move to
an overflow menu showing each item's image and title.
- keep ActionItem text as the UIBarButtonItem title when an icon is set. Bars still show only
the image; the title gives the overflow menu something to display.
- add ios.visibilityPriority ('high' | 'standard' | 'low' | number) to control which items
move to the overflow menu first.
- add ios.axisBehavior ('automatic' | 'horizontalOnly' | 'verticalPreferred') to control
whether an item may be presented in a vertical bar.
Both settings are ignored where the API is unavailable. Includes a toolbox page and device
tests.
Apply tools/notes/CodeComments.md to the iPhone Duo changes: single-sentence JSDoc with the Apple documentation link, inline comments of at most 12 words, and none where the code reads on its own. Drop the redundant string coercion of ActionItem text.
…ut slot A Frame or TabView placed directly in a NativeScript view (TabView as Page content, Frame in a GridLayout cell) was never positioned by core, so its controller's view kept the UIScreen.main bounds UIKit created it with. On iPhone Duo that is the outer display, so a TabView launched on the inner display rendered outer-sized. Sizing to the container's bounds also ignored the view's own cell, e.g. a Frame in the second row of a grid covered the first. When hosted in a NativeScript view, both now take the regular View frame path and extend the edges that sit on the container's safe area out to its bounds, since the controller insets its own content. Frames and TabViews under a UIKit container (window root, TabView items, modals) are unchanged.
NathanWalker
force-pushed
the
feat/iphone-duo-support
branch
from
October 2, 2026 19:44
8a04a21 to
1c40591
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.
PR Checklist
No issue exists yet; this comes out of building an app against the iPhone Duo simulator (Xcode 27.1 / iOS 27.1) and hitting the problems below. Reference: Preparing your app for iPhone Duo.
What is the current behavior?
iPhone Duo has two displays. A scene moves between them as the device opens and closes, and
UIScreen.mainis always the outer display (there are twoUIScreens while open;window.screenfollows the scene). Core assumes the scene matches the main screen in two places:1. A Frame or TabView hosted in a NativeScript view is never laid out. On iOS both have no-op
layoutNativeView()/_setNativeViewFrame(), which is right when a UIKit container controller places them (window root, TabView items, modals). When one is hosted directly in a NativeScript view instead — a TabView as a Page's content (the usual AngularTabViewin a routed component), or a Frame in a layout (GridLayout > page-router-outlet) — nothing sizes or positions the controller's view. It keeps theUIScreen.mainbounds UIKit creates it with, at the container's origin; the autoresizing mask then only tracks later container changes. This affects every device: a Frame or TabView in<GridLayout rows="50, *">row 1 covers row 0. On iPhone Duo it also means the wrong display's size. Measured inapps/toolboxwith its root Frame wrapped in a GridLayout, cold-launched with the device open:A TabView as Page content shows the same thing: launched on the inner display it stays 466 wide, with the vertical bar in the middle of the screen. Launching closed and then opening happens to work, which hides the problem.
2.
Screen.mainScreengoes stale. It caches the firstUIScreenit resolves, so after the scene moves displayswidthDIPs/heightDIPs(and the pixel sizes media queries match against) keep reporting the previous display:951x669while on the466x678outer display.3. ActionItems can't take part in vertical bars well. iOS 27.1 presents bars vertically on iPhone Duo, where fewer items fit and the rest move to an overflow menu that shows image + title. Core creates image-only
UIBarButtonItems wheniconis set (thetextis only used for accessibility), and there is no way to reach the newvisibilityPriority/axisBehaviorproperties.What is the new behavior?
fix(ios)Frame and TabView: when hosted directly in a NativeScript view (IOSHelper.isHostedInView), they take the regular View frame path from their layout slot. Their safe-area handling differs from other views: an edge that lies on the container's safe-area edge extends out to the container's bounds (IOSHelper.extendUnderContainerSafeArea), since the controller insets its own content.expandBeyondSafeAreacan't be reused here because it measures againstview.viewController.view, which for these views is the view itself. Frames and TabViews under a UIKit container (window root, TabView items, SplitView, modals) are untouched.fix(ios)Screen: bounds are read from the window's current screen.scalestill uses the cached screen: it is on hot paths and is identical on both displays, the invariantlayout-helperalready relies on.feat(action-bar):textis kept as theUIBarButtonItemtitle when aniconis set. Bars still render only the image (checked on iOS 26.5 and 27.1); the overflow menu now has a title to show.ios.visibilityPriority:'high' | 'standard' | 'low' | numberios.axisBehavior:'automatic' | 'horizontalOnly' | 'verticalPreferred'respondsToSelector). The 27.1 symbols aren't intypes-iosyet, so they are declared locally inaction-bar/index.ios.ts; those declarations can go once the typings are regenerated against the 27.1 SDK.Verified on the iPhone Duo simulator with the new
apps/toolboxaction-items page: with a horizontal and a vertical bar both present, thehorizontalOnlyitem stays in the horizontal bar while the rest stack vertically; with the closed device in landscape (the tightest bar) bothlowitems and the laststandardone moved to the overflow menu, listed by icon and title, while thehighitem kept its place.Testing
npx nx run core:test— passing.apps/automatedon an iPhone 17 Pro simulator (iOS 26.5) —1820 OK, 0 failed, including:frame-tests.ios.ts): each is hosted in row 1 of<GridLayout rows="50, *">and must start below the header and fill the rest of the grid. Both fail without the fix (Actual: 0 Expected: 112).UIScreen.main, which needs the iPhone Duo simulator.TabViewas Page content and no layout workarounds: cold launch on the open inner display, close, reopen, and book posture all give a TabView frame equal to its container. The same app with its rootpage-router-outletin row 1 of aGridLayoutunder a header keeps the header visible on the outer display. To reproduce the Frame case in toolbox, wrap the root Frame ofapps/toolbox/src/app-root.xmlin aGridLayoutand cold-launch with the device open.Not in this PR
UILayoutViewControllercopies an ancestor's top safe-area inset onto itself assuming its owner is a TabView item; any other hosted view with a parent picks that up too.types-iosfor the 27.1 SDK, and any first-class API for hinge state or reserved regions.