[iOS] NavigationPage: Fix large-title collapse for wrapped scroll content - #36511
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Ensure non-Shell NavigationPage pages with root layout wrappers around scrollable content can still collapse large titles into a visible standard title on iOS 26. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Handle default content wrappers when applying the iOS 26 non-Shell large-title root-layout safe-area adjustment and invalidate affected descendant safe-area caches. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36511Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36511" |
There was a problem hiding this comment.
Pull request overview
This PR completes the iOS 26 large-title collapse fix for non-Shell NavigationPage scenarios by ensuring the collapsed (standard) title remains visible, and by adjusting safe-area behavior for iOS 26 translucent navigation bars in specific non-Shell layouts. It also adds UITest coverage to prevent regressions across several common non-Shell content hierarchies.
Changes:
- Sync
UIViewController.TitlewithNavigationItem.Titleso the standard title remains visible after a large-title collapse in non-Shell navigation. - Re-evaluate/adjust safe-area handling for iOS 26 translucent non-Shell
NavigationPagepages (including invalidating safe-area caches whenSafeAreaEdgeschanges). - Add iOS UITest + HostApp scenarios covering ScrollView/ListView/CollectionView and common wrapper layouts, with iOS 26 visual baselines.
Show a summary per file
| File | Description |
|---|---|
| src/Core/src/Handlers/View/ViewHandler.iOS.cs | Invalidates iOS safe-area caches when SafeAreaEdges updates so runtime changes re-evaluate correctly. |
| src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs | Fixes non-Shell title collapse visibility by syncing controller title; adds iOS 26 large-title safe-area adjustment + invalidation logic. |
| src/Compatibility/Core/src/iOS/Renderers/NavigationRenderer.cs | Mirrors the title-sync fix for the legacy compatibility renderer path. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue33037NonShell.cs | Adds HostApp repro/coverage pages for multiple non-Shell scenarios (direct scrollables + wrapped layouts). |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33037NonShell.cs | Adds iOS UITest regression coverage with screenshots after scrolling for each non-Shell scenario. |
Copilot's findings
- Files reviewed: 5/10 changed files
- Comments generated: 0
|
@AlleSchonWeg the PR artifacts should become available from the checks on this PR. Please test them when you get a chance and let us know whether they resolve the remaining non-Shell NavigationPage large-title scenarios. Thank you! |
This comment has been minimized.
This comment has been minimized.
Keep the non-Shell iOS 26 large-title workaround scoped to the top safe area so bottom and side container insets continue to apply. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/azp run |
This comment has been minimized.
This comment has been minimized.
|
/review -b improved-reviewer -p ios |
1 similar comment
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 9 findings
See inline comments for details.
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 11 findings
See inline comments for details.
AI Review Summary
🗂️ Review Sessions — click to expand🚦 Gate — Test Before & After FixGate Result: ✅ PASSEDPlatform: IOS · Base: main · Merge base:
🔴 Without fix — 🖥️ Issue33037NonShell: FAIL ✅ · 340sError-relevant lines (filtered from the build log): 🟢 With fix — 🖥️ Issue33037NonShell: PASS ✅ · 152s(no coded error found; showing last 1200 chars) 📁 Fix files reverted (3 files)
📋 Pre-Flight — Context & ValidationIssue: #33037 - iOS Large Title display disappears Key Findings
Code Review SummaryVerdict: NEEDS_CHANGES Key code review findings:
Fix Candidates
🔬 Code Review — Deep AnalysisCode Review — PR #36511Independent AssessmentWhat this changes: Three things bundled together.
Inferred motivation: iOS 26 changed nav-bar/safe-area interaction; unless the page's root layout stops consuming the top inset, the scroller never scrolls "under" the bar, so the large title never collapses and the standard title never appears. Reconciliation with PR NarrativeAuthor claims: Completes the iOS 26 large-title fix for non-Shell Agreement/disagreement:
Prior Review ReconciliationLatest MauiBot round is
Per Rule #5, these override my own assessment. Blast Radius AssessmentTriggered by: handler/platform plumbing,
CI Status
Findings❌ Error — Snapshot baselines exist only for
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Explicitly designate UIKit's top-edge content scroll view using a weakly cached, bounded native-view resolver | ❌ FAIL (3/6) | 1 file | Collapse worked in all six cases, but wrapped-content screenshots failed because content still did not extend under the bar |
| 2 | try-fix | Transfer consumed top safe-area geometry to one structurally top-flush ScrollView during platform arrange | ❌ FAIL (3/6) | 4 files | Two runtime iterations reproduced Candidate #1's exact wrapped-case snapshot differences; handoff had no observable geometry effect |
| PR | PR #36511 | Apply handler-specific top safe-area opt-out to a root layout containing scrollable content, recursively invalidate safe-area caches, and synchronize the native controller title | ✅ PASSED (Gate) | 3 files | Original PR; unresolved whole-root geometry and descendant-removal hot-path concerns |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-5 | 1 | Yes | Combine proven UIKit scroll-view designation with a narrowly scoped geometry adjustment that affects only the designated scroller path, not the entire root layout |
| gpt-5.6-sol | 1 | Yes | Transfer only the consumed top geometry to a structurally top-flush ScrollView during platform arrange |
| Both | 2 | Skipped | The bounded Phase 2 budget was reached after two complete expert-review, implementation, and iOS test cycles |
Exhausted: Yes — both required model families tested meaningfully different root-cause strategies. Further work would require instrumenting compatibility-renderer arrange order rather than generating another evidence-free variation.
Selected Fix: PR's fix — it is the only implementation that passes all six supplied iOS scenarios. Neither alternative is demonstrably better; the PR's independent code-review concerns remain unresolved.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the current title lacks the required platform/component form, and the description is stale because the winning fix removes UIViewController.Title synchronization, narrows safe-area eligibility, and expands coverage from the stated 5/5 to 9 scenarios.
Recommended title
[iOS] NavigationPage: Fix non-Shell large-title collapse
Recommended description
Fixes #33037
## Summary
This completes the iOS 26 large-title fix for non-Shell `NavigationPage` scenarios.
### Root cause
For wrapped non-Shell content, the root layout consumed the top safe area before its scrollable child reached UIKit. The scrollable content therefore did not extend under the translucent navigation bar, preventing iOS 26 from consistently materializing the collapsed standard title.
### Fix
- In `src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs`, applies a handler-specific top `SafeAreaEdges` opt-out only when the page resolves to one visible, top-edge `ScrollView`, `ListView`, `ItemsView`, or `TableView` path under a visible translucent navigation bar with an effective large title.
- Preserves explicit app `SafeAreaEdges`, fixed siblings, top padding/margins, reserved `Grid` rows, hidden navigation bars, and pages whose effective large-title mode is inline.
- Re-evaluates dynamic content and layout eligibility without scanning recycled collection cells on the scroll path, and restores the override during page and renderer teardown.
- In `src/Core/src/Handlers/View/ViewHandler.iOS.cs`, invalidates safe-area caches for descendant MAUI visual-tree views when `SafeAreaEdges` changes.
- Limits the workaround to iOS 26; MacCatalyst is not enabled without dedicated coverage.
## Existing PR comparison
PR #33246 fixed the Shell path. This PR covers the remaining non-Shell `NavigationPage` path and adds regression coverage for the non-Shell scenarios that were still broken.
## What not to do
- Do not rely only on `SetContentScrollView`: it collapses the title but leaves wrapped scroll frames below the navigation bar, failing 3/6 visual scenarios.
- Do not hand off the frame only in `ScrollViewHandler.PlatformArrange`: that approach also failed the same 3 wrapped scenarios and introduced ambiguous safe-area edge ownership.
- Do not opt out the entire root layout merely because any descendant scrolls; fixed siblings can then render under the navigation bar.
## Verification
- Reproduced the broken non-Shell behavior on an iOS 26 Simulator.
- The supplied gate failed without the fix and passed all 6 original scenarios with the PR fix.
- Ran `BuildAndRunHostApp.ps1 -Platform ios -TestFilter "FullyQualifiedName~Issue33037NonShell"` against the reviewer-enhanced fix — 9/9 passed.
- Coverage includes direct `ScrollView`, `Grid` + `ScrollView`, `ContentView` + `Grid` + `ScrollView`, dynamically added wrapped content, `ListView`, and `CollectionView`, plus negative cases for a fixed header, a reserved `Grid` row, and a fixed sibling shown at runtime.
- Added iOS 26 visual baselines and gated the visual test to iOS 26.
@AlleSchonWeg please test the PR artifacts when they are available and let us know whether this resolves the remaining non-Shell scenarios for you.
🏁 Report — Final Recommendation
Comparative Report
| Rank | Candidate | Regression result | Assessment |
|---|---|---|---|
| 1 | pr-plus-reviewer |
PASS: 9/9 | Preserves the PR's 6 passing scenarios, passes 3 new negative cases, and addresses the expert review's whole-root geometry, cell-recycling hot-path, hidden-bar, inherited-mode, cache-invalidation, baseline, and title-sync concerns. |
| 2 | pr |
PASS: 6/6 gate | Only submitted candidate proven to fail without the fix and pass with it, but it retains 11 expert findings, including fixed-sibling overlap and collection-recycling work on the scroll path. |
| 3 | try-fix-1 |
FAIL: 3/6 | Explicit UIKit content-scroll-view designation collapses all titles, but wrapped layouts still fail visual regression because their scroll frames remain below the navigation bar. |
| 4 | try-fix-2 |
FAIL: 3/6 | Arrange-time top-edge frame handoff has no observable effect in the three wrapped cases and additionally risks treating top-edge ownership as ownership of every safe-area edge. |
Comparison
try-fix-1 establishes that SetContentScrollView solves only UIKit's offset discovery; it does not make wrapped content extend under the bar. try-fix-2 attempts the missing geometry handoff but reproduces the same 3 wrapped snapshot failures. Both therefore rank below the two candidates that pass the regression matrix.
The raw PR is functionally effective, but its broad HasScrollableContent heuristic applies Top=None to non-scrolling siblings, and its removed-descendant filter fails after parent detachment. Its pre-iOS-26 baseline handling and several fixtures are also incomplete.
pr-plus-reviewer narrows eligibility to a single visible top-edge scroll path, handles runtime transitions, invalidates descendant safe-area caches through the MAUI visual tree, removes unrelated title synchronization, and adds discriminating negative coverage. It passes the original 6 scenarios plus all 3 added regressions.
Winner
pr-plus-reviewer
It is the only candidate that combines a passing positive regression matrix with passing coverage for the principal adjacent regressions identified during expert review. MacCatalyst remains intentionally excluded pending dedicated validation.
📱 UI Tests — CollectionView,Layout,ListView,Navigation,SafeAreaEdges,ScrollView,ViewBaseTests
Detected UI test categories: CollectionView,Layout,ListView,Navigation,SafeAreaEdges,ScrollView,ViewBaseTests
❌ Deep UI tests — 750 passed, 1 failed across 6 categories on platform-pool agent (replaces in-process counts above). The deep UI run for 1 category (CollectionView) exceeded the per-category time budget (a very long-running category or a slow build/deploy) and was stopped before finishing. This is an infrastructure/timeout issue, not a code problem; re-run the review, and if a category consistently needs more time the per-category budget can be raised (DEEP_UITEST_CATEGORY_CAP_MIN / DEEP_UITEST_HARDSTOP_MIN). See the build-output.log in the drop-deep-uitests artifact.
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
Layout |
200/206 (1 ❌) | — |
ListView |
97/99 ✓ | — |
Navigation |
116/119 ✓ | — |
SafeAreaEdges |
67/67 ✓ | — |
ScrollView |
158/159 ✓ | — |
ViewBaseTests |
112/112 ✓ | — |
🔍 AI analysis of failures — PR-related vs unrelated
🔍 AI-generated triage (GitHub Copilot CLI) — a heuristic judgement of whether each deep UI test failure is connected to this PR's changes. Verify before relying on it.
Likely PR-related: one or more failures appear connected to this PR's changes.
- ✗ PR-related — iOS large-title keyboard-scrolling layout (~1 test):
EditorsScrollingPageTestexercises a non-scrolling ContentView page with iOS large titles, directly overlapping the PR's NavigationPage large-title safe-area and descendant-layout changes that could affect element visibility.
❌ Layout — 1 failed test
EditorsScrollingPageTest
OpenQA.Selenium.NoSuchElementException : An element could not be located on the page using the given search parameters.; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.Appium.AppiumDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.FindElement(String mechanism, String value)
at OpenQA.Selenium.Appium.AppiumDriver.FindElement(String by, String value)
at OpenQA.Selenium.Appium.MobileBy.FindElement(ISearchContext context)
at OpenQA.Selenium.WebDriver.FindElement(By by)
at OpenQA.Selenium.Appium.AppiumDriver.FindElement(By by)
at Microsoft.Maui.TestCases.Tests.KeyboardScrolling.CloseiOSEditorKeyboard(AppiumDr
...
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)
🧭 Next Steps — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
Mirror wrapped scroll offsets through a transparent direct-child UIScrollView on iOS 26 so UIKit can collapse NavigationPage large titles without changing app safe-area behavior. Cover fixed siblings, short content, dynamic content, CollectionView, and programmatic/restored offsets with simulator UI tests and visual baselines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b9546732-a28a-4b72-9928-aaeb2601f1d3
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33037NonShell.cs:26
scenarioNameandfixedHeaderIdare intentionally passed asnullin several[TestCase]attributes, but the method signature declares them as non-nullablestring. This makes the nullability contract misleading and can cause accidental null dereferences if the method body changes later.
Make these parameters nullable to reflect actual usage.
public void LargeTitleCollapsesToVisibleStandardTitle(string buttonId, string scrollerId, string title, string targetItem, string scenarioName, string fixedHeaderId)
kubaflo
left a comment
There was a problem hiding this comment.
Expert review — see inline comments.
Cache the resolved vertical scroller, skip hidden and horizontal candidates, and support wrapped TableView and WebView content with focused iOS regression coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b9546732-a28a-4b72-9928-aaeb2601f1d3
Review feedback follow-up — empirically validatedPushed
Added simulator coverage for wrapped Validated on a clean iPhone 11 Pro / iOS 26.1 (23B86) simulator with: pwsh .github/scripts/BuildAndRunHostApp.ps1 \
-Platform ios \
-TestFilter "FullyQualifiedName~Issue33037NonShell" \
-DeviceUdid "CB7862CA-0D12-426D-A7E2-C8F80723B32F"The complete before/after video and screenshot evidence, including fixed-header, hidden-navigation-bar, and A fresh MAUI expert review of the final diff found no major, moderate, or merge-blocking findings. All three review threads have been replied to and resolved. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs:1692
NeedsLargeTitleProxyScrollViewis gated byOperatingSystem.IsIOSVersionAtLeast(26)only. That API also returns true on MacCatalyst, so this proxy behavior will run on MacCatalyst 26+ even though the PR description/tests scope it to iOS 26 only. If this fix is intended to be iOS-only, explicitly exclude MacCatalyst here (or update the PR description/tests to cover MacCatalyst).
bool NeedsLargeTitleProxyScrollView()
{
if (!OperatingSystem.IsIOSVersionAtLeast(26) || Child is null)
return false;
src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs:1965
FindPlatformScrollViewreturns the firstUIScrollViewit encounters in a depth-first traversal, even if that scroll view is hidden/transparent. Since callers later reject ineligible scroll views (hidden/alpha), this can cause wrapped scrollables to be missed entirely when a hidden scroll view appears first in the subview tree. Prefer searching for the first eligible scroll view and skip hidden branches during traversal.
static UIScrollView FindPlatformScrollView(UIView view)
{
var subviews = view.Subviews;
for (int i = 0; i < subviews.Length; i++)
{
|
Hi @jfversluis , Thank you |








Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description of Change
Fixes non-Shell
NavigationPagelarge titles on iOS 26 when the page's scrollable content is wrapped by aGrid,ContentView, or another layout.Root Cause
UIKit collapses a large navigation title by observing a direct, visible
UIScrollViewowned by the page controller. When MAUI content wraps aScrollView,ListView,CollectionView,TableView, orWebView, the native scroller is nested below another view, so UIKit does not react to its offset.Fix
On iOS 26 only, wrapped scrollers are mirrored through a transparent, non-interactive direct-child
UIScrollViewbehind the real content. The proxy:CollectionView's delegate;CollectionViewplatform views andWKWebView.ScrollViewexplicitly;ScrollView,ListView,CollectionView,TableView, andWebViewcontent;-AdjustedContentInsetoffset when observation is armed, so restored orOnAppearingprogrammatic offsets collapse correctly;Direct root scrollers continue using UIKit's native behavior. Hidden navigation bars and app safe-area settings are not modified.
Regression Coverage
The iOS UI suite covers:
Grid/ContentView-wrappedScrollView;ListView, Items2CollectionView,TableView, andWebView;CollectionView.ScrollTo;OnAppearing/restored programmatic offsets;What NOT to Do
SafeAreaEdgesto force UIKit large-title behavior; that moves fixed siblings and changes unrelated app layout.UIScrollView.ScrolledforCollectionView; event registration replaces its existing native delegate and crashes.Verification
BuildAndRunHostApp.ps1 -Platform ios -TestFilter "FullyQualifiedName~Issue33037NonShell" -DeviceUdid CB7862CA-0D12-426D-A7E2-C8F80723B32F— 14/14 passed.Existing PR Comparison
PR #33246 fixed the Shell path. This PR covers the remaining non-Shell
NavigationPagepath.Issues Fixed
Fixes #33037