Repository navigation
[iPhone Duo] Use the correct UIScreen to report the display size and update when fold/unfold - #195199
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates FlutterEngine and SemanticsObject to retrieve display information from the screen the view is currently on, falling back to the main screen only when the view is not yet in a window. It also prevents redundant display updates. The review feedback suggests simplifying the screen retrieval by using window.screen directly instead of window.windowScene.screen, which would also allow for simpler mock setups in the corresponding unit tests.
| * Sends the current display information (e.g. the size of the screen that the view controller's | ||
| * view is on) to the framework. |
There was a problem hiding this comment.
nit: technically only sends the info if the size/scale differ.
| * Sends the current display information (e.g. the size of the screen that the view controller's | |
| * view is on) to the framework. | |
| * Sends display information for the screen that the view controller's view is on, if it differs | |
| * from the display last sent to the framework. | |
| * | |
| * If the view isn't in a window, keeps the last display sent. If no display has been sent yet, | |
| * sends `UIScreen.mainScreen` as a best-effort initial display. |
cbracken
left a comment
There was a problem hiding this comment.
lgtm with a suggestion and some followup work.
| [self checkAndUpdateAutoResizeConstraints]; | ||
| [self setViewportMetricsPaddings]; | ||
| [self updateViewportMetricsIfNeeded]; | ||
| [self updateEngineDisplays]; |
There was a problem hiding this comment.
We definitely need to do it here, but viewDidLayoutSubviews only runs when we need relayout. Is there any guarantee that happens here in every case?
The docs say it's called just after the view controller's view's layoutSubviews is invoked. That'll happen if it's bounds change which is definitely the case in fullscreen apps, but what if we have a FlutterView with a fixed width and height e.g. in an add-to-app scenario?
The SDK docs say windowScene:didUpdateEffectiveGeometry: is called whenever a UIWindowScene moves between screens, so in theory that's probably the right thing to do, but would require us to update FlutterSceneDelegate and FlutterPluginSceneLifeCycleDelegate to call updateDisplays. Doesn't work for apps that haven't added FlutterSceneDelegate though. Do we require that for add-to-app? It looks like no, we have FlutterSceneLifeCycleProvider for those cases. In those cases, I'd think they probably need their own migration if we add additional overrides to it, so that's probably a separate followup PR.
A quick fix might be to register to fire on trait changes in viewDidLoad? Size classes are inherited from the window, so even a fixed-size flutter view would get the callback. Something like:
if (@available(iOS 17.0, *)) {
NSArray<UITrait>* sizeClassTraits =
@[ [UITraitHorizontalSizeClass class], [UITraitVerticalSizeClass class] ];
[self registerForTraitChanges:sizeClassTraits withAction:@selector(updateEngineDisplays)];
}
No need for an availability fallback for pre-iOS 17 since the Duo runs on 27.1 or later.
There was a problem hiding this comment.
@cbracken Thanks for this review! I did some testing and here's what I found:
For a fixed-size 300x300 FlutterView, viewDidLayoutSubviews is called when switching from outer to inner display, but not called when switching from inner to outer display. This is a puzzling behavior (possibly a bug in UIKit), but it does prove that you are right that relying on viewDidLayoutSubviews is not complete.
For size class registration, there are a few issues:
- the callback is called before
windowScene.screenis changed. This could be fixed by updating it in the next run-loop (with adispatch_async). - size class is tied to window, rather than screen, so it could fail for non-full screen windowed app
- even for full screen apps, in the future apple could introduce a new device with 2 screens of the same size class.
So I'm gonna go with didUpdateEffectiveGeometry approach in the next PR. There won't be a breaking change, but it would require FlutterSceneLifeCycleProvider users to add a one-liner, and we should update our documentation too.
func windowScene(_ windowScene: UIWindowScene,
didUpdateEffectiveGeometry previousEffectiveGeometry: UIWindowScene.Geometry) {
sceneLifeCycleDelegate.windowScene(windowScene, didUpdateEffectiveGeometry: previousEffectiveGeometry)
}
|
Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change). If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
autosubmit label was removed for flutter/flutter/195199, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label. |
|
autosubmit label was removed for flutter/flutter/195199, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label. |
|
autosubmit label was removed for flutter/flutter/195199, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label. |
|
autosubmit label was removed for flutter/flutter/195199, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label. |
|
Golden file changes are available for triage from new commit, Click here to view. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
b5d4f55 to
839255f
Compare
flutter/flutter@d4c8577...0c2473c 2026-10-28 [email protected] Roll Packages from a8549ae to 13d38e0 (9 revisions) (flutter/flutter#195488) 2026-10-28 [email protected] Use int.oneBitCount in PersistentHashMap and scope 53-bit caps to JS (flutter/flutter#195005) 2026-10-28 [email protected] Roll pub packages (flutter/flutter#195483) 2026-10-28 [email protected] [iPhone Duo] Use the correct UIScreen to report the display size and update when fold/unfold (flutter/flutter#195199) 2026-10-28 [email protected] [fuchsia] Fix portable_ui_test.h includes for newer Fuchsia SDKs (flutter/flutter#195420) 2026-10-28 [email protected] Change default pipeline options to match production draw calls (flutter/flutter#194597) 2026-10-28 [email protected] Roll Fuchsia Test Scripts from 2T18KC7-LDQtmQiLP... to xl1LWBuppzu-BnOzV... (flutter/flutter#195450) 2026-10-27 [email protected] [flutter_tools] Inject dependencies into Darwin build targets, native assets, and Xcode build settings (flutter/flutter#194052) 2026-10-27 [email protected] Revert: Reland square corners fix (flutter/flutter#195436) 2026-10-27 [email protected] [flutter_tools] Add DeviceService app launch and flutter run support for tool extensions (flutter/flutter#194589) 2026-10-27 [email protected] [engine] Honor targetWidth/targetHeight for animated images (flutter/flutter#194615) 2026-10-27 [email protected] [Android][a11y] Set visibleToUser=false for hidden semantics nodes (flutter/flutter#194411) 2026-10-27 [email protected] Fix repo deletion test flake (flutter/flutter#195349) 2026-10-27 [email protected] Roll Packages from 23a05b6 to a8549ae (10 revisions) (flutter/flutter#195434) 2026-10-27 [email protected] [web] Capture pointers to the Flutter view so drags survive platform view iframes (flutter/flutter#195187) 2026-10-27 [email protected] [web] Add opt-in raw samples, measured sample count and warm-up phase hook to the web benchmark recorder (flutter/flutter#195118) 2026-10-27 [email protected] Link migration guide in deprecation messages (flutter/flutter#195361) 2026-10-27 [email protected] Pin basic_material_app_macos__compile to macOS 26 (flutter/flutter#195429) 2026-10-27 [email protected] Add benchmark for warmed pipeline usage (flutter/flutter#195001) 2026-10-27 [email protected] [flutter_tools] Add device log streaming support to DeviceService and ExtensionBackedDevice (flutter/flutter#194964) 2026-10-27 [email protected] Roll Dart SDK from 1d16c04dc346 to 65fc979759ca (4 revisions) (flutter/flutter#195383) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC [email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
…update when fold/unfold (flutter#195199) On iPhone Duo, `UIScreen.mainScreen` always refers to the outer display. The iOS embedder used `UIScreen.mainScreen`, so when the app is on the inner display, `PlatformDispatcher.displays` reports the wrong screen. This PR ensures the embedder to report the correct screen size to the framework, and when switch between inner/outer screens, update the framework with the new size. ## Why still need `UIScreen.mainScreen` fallback The framework can read display information as soon as the isolate launches, before any view is in a window (see this test project flutter#195177 (comment)). At that point we do not know which screen the FlutterVC will be added to (if we ever add it to the screen). MainScreen remains a best-effort initial guess (better than a `.zero` size since flutter developers may have already depend on the value). It is corrected as soon as the view is laid out in a window. `_hasSentDisplays` makes the engine use the `UIScreen.mainScreen` fallback only for the very first display it sends, before any view is in a window. After that, if the view isn't in a window (e.g. a newly attached view controller whose view isn't loaded yet), the engine keeps the last reported display instead of replacing it with the `mainScreen` guess. The flag resets when a new shell is adopted, so each new shell still gets an initial display. ## Why not fallback based on hinge state? 1. Hinge API only works for a view that's already in a window. 2. There's no dedicated API to get the inner display (unless with another deprecated `UIScreen.screens` API which isn't great either). 3. Open state could use both inner and outer displays (accessory scene) ## Why change in `SemanticsObject` is simpler than `FlutterEngine/VC` Because `SemanticsObject` is a "pull" model, where we simply need to check which screen when accessing it. On the other hand, `FlutterEngine/VC` is a "push" model, where we have to make sure to update the value when screen changes. ## Integration test I don't think there's a `simctl` sub-command that allows us to fold/unfold the simulator. We could add a integration test in the future if such api become available. ## Manual test I have manually verified using [this test project](flutter#195177 (comment)), and confirmed the reported value is correct, and updated correctly once fold/unfold Scenario | Window screen | Before (stock engine) | After (this PR) -- | -- | -- | -- Launch closed | 1398x2034 | 1398x2034 | 1398x2034 Closed → open | 2007x2853 | ❌ 1398x2034 | ✅ 2007x2853 Open → closed | 1398x2034 | 1398x2034 | ✅ 1398x2034 Launch open (first read in main()) | 2007x2853 | ❌ 1398x2034 for the whole session | ✅ 2007x2853 Working towards flutter#195177 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
|
Could a maintainer consider adding Environment: Flutter 3.47.5 stable (engine
Coordinate taps from the element frame work around the XCUITest failure. VoiceOver/Voice Control impact on a physical device has not been tested. |
|
@StDantist will discuss with the team. |
This is a follow up to flutter#195199 As discussed [in this thread](flutter#195199 (comment)), For a fixed-size FlutterView, `viewDidLayoutSubviews` is called when switching from outer to inner display, but not called when switching from inner to outer display. This is a puzzling behavior (possibly a bug in UIKit), but it does prove that relying on `viewDidLayoutSubviews` is not complete. The only API we can find that triggers when a scene changes screen is `didUpdateEffectiveGeometry`. There won't be a breaking change, but it would require `FlutterSceneLifeCycleProvider` users to add a one-liner for this to work. We should update our website to include this ([PR](flutter/website#14012)): ``` @available(iOS 26.0, *) func windowScene(_ windowScene: UIWindowScene, didUpdateEffectiveGeometry previousEffectiveGeometry: UIWindowScene.Geometry) { sceneLifeCycleDelegate.windowScene(windowScene, didUpdateEffectiveGeometry: previousEffectiveGeometry) } ``` ## Testing I have manually verified with a fixed-size 300x300 FlutterView, and confirmed the information is correctly updated when switching screen. It is not possible to write integration test - I don't think there's a `simctl` sub-command that allows us to fold/unfold the simulator. We could add a integration test in the future if such api become available. Fixes flutter#195177 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

On iPhone Duo,
UIScreen.mainScreenalways refers to the outer display. The iOS embedder usedUIScreen.mainScreen, so when the app is on the inner display,PlatformDispatcher.displaysreports the wrong screen.This PR ensures the embedder to report the correct screen size to the framework, and when switch between inner/outer screens, update the framework with the new size.
Why still need
UIScreen.mainScreenfallbackThe framework can read display information as soon as the isolate launches, before any view is in a window (see this test project #195177 (comment)). At that point we do not know which screen the FlutterVC will be added to (if we ever add it to the screen).
MainScreen remains a best-effort initial guess (better than a
.zerosize since flutter developers may have already depend on the value). It is corrected as soon as the view is laid out in a window._hasSentDisplaysmakes the engine use theUIScreen.mainScreenfallback only for the very first display it sends, before any view is in a window. After that, if the view isn't in a window (e.g. a newly attached view controller whose view isn't loaded yet), the engine keeps the last reported display instead of replacing it with themainScreenguess. The flag resets when a new shell is adopted, so each new shell still gets an initial display.Why not fallback based on hinge state?
UIScreen.screensAPI which isn't great either).Why change in
SemanticsObjectis simpler thanFlutterEngine/VCBecause
SemanticsObjectis a "pull" model, where we simply need to check which screen when accessing it. On the other hand,FlutterEngine/VCis a "push" model, where we have to make sure to update the value when screen changes.Integration test
I don't think there's a
simctlsub-command that allows us to fold/unfold the simulator. We could add a integration test in the future if such api become available.Manual test
I have manually verified using this test project, and confirmed the reported value is correct, and updated correctly once fold/unfold
Working towards #195177
If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.