-
Notifications
You must be signed in to change notification settings - Fork 2k
[iOS] NavigationPage: Fix large-title collapse for wrapped scroll content #36511
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
kubaflo
merged 11 commits into
inflight/current
from
jfversluis-fix-ios-large-title-nonshell
Aug 9, 2026
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
1c3baaf
Fix iOS non-Shell large title collapse
jfversluis f3df717
Fix non-Shell large title test build
jfversluis 1d30e1c
Avoid iOS-specific type ambiguity in large title test
jfversluis ec19807
Adjust non-Shell large title regression
jfversluis a2fc6e0
Fix iOS 26 large titles in root layouts
jfversluis 6708195
Cover wrapped iOS large title layouts
jfversluis 72a940e
Preserve non-top safe areas for large titles
jfversluis 9de9c88
Handle dynamic iOS large-title content
jfversluis 08208d6
Track iOS large-title safe-area overrides
jfversluis 1ac530d
[iOS] Fix wrapped non-Shell large title collapse
Copilot b1da04e
[iOS] Harden wrapped large-title scroll discovery
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
344 changes: 344 additions & 0 deletions
344
src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
Large diffs are not rendered by default.
Oops, something went wrong.
625 changes: 625 additions & 0 deletions
625
src/Controls/tests/TestCases.HostApp/Issues/Issue33037NonShell.cs
Large diffs are not rendered by default.
Oops, something went wrong.
190 changes: 190 additions & 0 deletions
190
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33037NonShell.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| #if IOS | ||
| using NUnit.Framework; | ||
| using UITest.Appium; | ||
| using UITest.Core; | ||
|
|
||
| namespace Microsoft.Maui.TestCases.Tests.Issues; | ||
|
|
||
| public class Issue33037NonShell : _IssuesUITest | ||
| { | ||
| public Issue33037NonShell(TestDevice testDevice) : base(testDevice) | ||
| { | ||
| } | ||
|
|
||
| public override string Issue => "iOS Large Title display disappears when scrolling in non-Shell NavigationPage"; | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.Navigation)] | ||
| [TestCase("Issue33037ScrollViewButton", "Issue33037ScrollViewScroller", "Issue33037 Direct", "Item 40", "DirectScrollView", null)] | ||
| [TestCase("Issue33037GridScrollViewButton", "Issue33037GridScrollViewScroller", "Issue33037 Grid", "Item 40", "GridScrollView", null)] | ||
| [TestCase("Issue33037ContentViewGridScrollViewButton", "Issue33037ContentViewGridScrollViewScroller", "Issue33037 Wrapped", "Item 40", "ContentViewGridScrollView", null)] | ||
| [TestCase("Issue33037DynamicContentViewGridScrollViewButton", "Issue33037DynamicContentViewGridScrollViewScroller", "Issue33037 Dynamic", "Item 40", "DynamicContentViewGridScrollView", null)] | ||
| [TestCase("Issue33037ListViewButton", "Issue33037ListViewScroller", "Issue33037 List", "Item 40", "ListView", null)] | ||
| [TestCase("Issue33037CollectionViewButton", "Issue33037CollectionViewScroller", "Issue33037 Collection", "Item 40", "CollectionView", null)] | ||
| [TestCase("Issue33037TableViewButton", "Issue33037TableViewScroller", "Issue33037 Table", "Item 40", null, null)] | ||
| [TestCase("Issue33037CandidateSelectionButton", "Issue33037CandidateSelectionScroller", "Issue33037 Candidates", "Item 40", null, null)] | ||
| [TestCase("Issue33037FixedHeaderCollectionViewButton", "Issue33037FixedHeaderCollectionViewScroller", "Issue33037 Fixed Header", "Item 40", "FixedHeaderCollectionView", "Issue33037FixedHeader")] | ||
| [TestCase("Issue33037ShortFixedHeaderCollectionViewButton", "Issue33037ShortFixedHeaderCollectionViewScroller", "Issue33037 Short Header", "Item 16", null, "Issue33037ShortFixedHeader")] | ||
| public void LargeTitleCollapsesToVisibleStandardTitle(string buttonId, string scrollerId, string title, string targetItem, string scenarioName, string fixedHeaderId) | ||
| { | ||
| RequireIOS26OrHigher(); | ||
| App.WaitForElement(buttonId).Click(); | ||
|
|
||
| try | ||
| { | ||
| var expandedTitleRect = App.WaitForElement(title).GetRect(); | ||
| App.WaitForElement(scrollerId); | ||
|
|
||
| if (buttonId == "Issue33037GridScrollViewButton") | ||
| { | ||
| VerifyScreenshot( | ||
| "Issue33037NonShell_GridScrollView_BeforeScroll", | ||
| tolerance: 0.5, | ||
| retryTimeout: TimeSpan.FromSeconds(2)); | ||
| } | ||
|
|
||
| App.ScrollDown(scrollerId, swipePercentage: 0.8); | ||
| App.ScrollDown(scrollerId, swipePercentage: 0.8); | ||
| App.WaitForElement(targetItem); | ||
|
|
||
| var collapsedTitle = App.WaitForElement(title, $"Timed out waiting for collapsed title '{title}'"); | ||
| var collapsedTitleRect = collapsedTitle.GetRect(); | ||
|
|
||
| Assert.That(collapsedTitleRect.Height, Is.GreaterThan(0), | ||
| $"The '{title}' navigation title should remain visible in the standard navigation bar after collapsing."); | ||
| Assert.That(collapsedTitleRect.Height, Is.LessThan(expandedTitleRect.Height), | ||
| $"The '{title}' navigation title should be shorter after the nested scroller moves."); | ||
| Assert.That(collapsedTitleRect.Height, Is.LessThan(60), | ||
| $"The '{title}' navigation title should use the collapsed standard-title size."); | ||
| Assert.That(collapsedTitleRect.Y, Is.LessThan(130), | ||
| $"The '{title}' navigation title should remain in the navigation bar after collapsing."); | ||
|
|
||
| if (!string.IsNullOrEmpty(fixedHeaderId)) | ||
| { | ||
| var fixedHeaderRect = App.WaitForElement(fixedHeaderId).GetRect(); | ||
| Assert.That(fixedHeaderRect.Y, Is.GreaterThanOrEqualTo(collapsedTitleRect.Y + collapsedTitleRect.Height - 2), | ||
| "The fixed header should remain below the collapsed navigation title."); | ||
| } | ||
|
|
||
| if (!string.IsNullOrEmpty(scenarioName)) | ||
| { | ||
| VerifyScreenshot( | ||
| $"Issue33037NonShell_{scenarioName}_AfterScroll", | ||
| cropBottom: fixedHeaderId is null ? 0 : 2016, | ||
| tolerance: fixedHeaderId is null ? 0.5 : 1.5, | ||
| retryTimeout: TimeSpan.FromSeconds(2)); | ||
| } | ||
| } | ||
| finally | ||
| { | ||
| App.Back(); | ||
| } | ||
| } | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.Navigation)] | ||
| public void ProgrammaticWebViewScrollCollapsesLargeTitle() | ||
| { | ||
| RequireIOS26OrHigher(); | ||
| App.WaitForElement("Issue33037WebViewButton").Click(); | ||
|
|
||
| try | ||
| { | ||
| App.WaitForElement("Ready"); | ||
| var expandedTitleRect = App.WaitForElement("Issue33037 Web").GetRect(); | ||
|
|
||
| App.WaitForElement("Issue33037WebViewScrollButton").Click(); | ||
| App.WaitForElement("Scrolled"); | ||
|
|
||
| var collapsedTitleRect = App.WaitForElement("Issue33037 Web").GetRect(); | ||
| Assert.That(collapsedTitleRect.Height, Is.LessThan(expandedTitleRect.Height), | ||
| "The navigation title should be shorter after the wrapped WebView scrolls."); | ||
| Assert.That(collapsedTitleRect.Height, Is.LessThan(60), | ||
| "The navigation title should collapse after the wrapped WebView scrolls."); | ||
| } | ||
| finally | ||
| { | ||
| App.Back(); | ||
| } | ||
| } | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.Navigation)] | ||
| public void ProgrammaticScrollCollapsesLargeTitle() | ||
| { | ||
| RequireIOS26OrHigher(); | ||
| App.WaitForElement("Issue33037ProgrammaticCollectionViewButton").Click(); | ||
|
|
||
| try | ||
| { | ||
| App.WaitForElement("Issue33037ProgrammaticCollectionViewScroller"); | ||
| var expandedTitleRect = App.WaitForElement("Issue33037 Programmatic").GetRect(); | ||
|
|
||
| App.WaitForElement("Issue33037ProgrammaticScrollButton").Click(); | ||
| App.WaitForElement("Item 50"); | ||
|
|
||
| var collapsedTitleRect = App.WaitForElement("Issue33037 Programmatic").GetRect(); | ||
| Assert.That(collapsedTitleRect.Height, Is.LessThan(expandedTitleRect.Height), | ||
| "The navigation title should be shorter after CollectionView.ScrollTo changes the wrapped scroller offset."); | ||
| Assert.That(collapsedTitleRect.Height, Is.LessThan(60), | ||
| "The navigation title should collapse after CollectionView.ScrollTo changes the wrapped scroller offset."); | ||
| } | ||
| finally | ||
| { | ||
| App.Back(); | ||
| } | ||
| } | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.Navigation)] | ||
| public void OnAppearingProgrammaticScrollCollapsesLargeTitle() | ||
| { | ||
| RequireIOS26OrHigher(); | ||
| App.WaitForElement("Issue33037AppearingCollectionViewButton").Click(); | ||
|
|
||
| try | ||
| { | ||
| App.WaitForElement("Issue33037AppearingCollectionViewScroller"); | ||
| App.WaitForElement("Item 50"); | ||
|
|
||
| var collapsedTitleRect = App.WaitForElement("Issue33037 Appearing").GetRect(); | ||
| Assert.That(collapsedTitleRect.Height, Is.LessThan(60), | ||
| "The navigation title should collapse when CollectionView.ScrollTo runs during OnAppearing."); | ||
|
|
||
| VerifyScreenshot( | ||
| "Issue33037NonShell_AppearingCollectionView_AfterScroll", | ||
| tolerance: 0.5, | ||
| retryTimeout: TimeSpan.FromSeconds(2)); | ||
| } | ||
| finally | ||
| { | ||
| App.Back(); | ||
| } | ||
| } | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.Navigation)] | ||
| public void HiddenNavigationBarPreservesTopSafeArea() | ||
| { | ||
| RequireIOS26OrHigher(); | ||
| App.WaitForElement("Issue33037HiddenNavigationBarButton").Click(); | ||
|
|
||
| try | ||
| { | ||
| var topMarker = App.WaitForElement("Issue33037HiddenNavigationBarTopMarker").GetRect(); | ||
| Assert.That(topMarker.Y, Is.GreaterThan(20), | ||
| "Content should remain below the status bar when the navigation bar is hidden."); | ||
| } | ||
| finally | ||
| { | ||
| App.WaitForElement("Issue33037HiddenNavigationBarBackButton").Click(); | ||
| } | ||
| } | ||
|
|
||
| void RequireIOS26OrHigher() | ||
| { | ||
| if (App is not AppiumIOSApp iosApp || !HelperExtensions.IsIOS26OrHigher(iosApp)) | ||
| Assert.Ignore("Issue #33037 only affects iOS 26 and later."); | ||
| } | ||
| } | ||
| #endif | ||
Binary file added
BIN
+82.7 KB
...sts/snapshots/ios-26/Issue33037NonShell_AppearingCollectionView_AfterScroll.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+88.1 KB
...es.iOS.Tests/snapshots/ios-26/Issue33037NonShell_CollectionView_AfterScroll.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+134 KB
...s/snapshots/ios-26/Issue33037NonShell_ContentViewGridScrollView_AfterScroll.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+133 KB
....iOS.Tests/snapshots/ios-26/Issue33037NonShell_DirectScrollView_AfterScroll.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+134 KB
...hots/ios-26/Issue33037NonShell_DynamicContentViewGridScrollView_AfterScroll.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+34 KB
...s/snapshots/ios-26/Issue33037NonShell_FixedHeaderCollectionView_AfterScroll.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+133 KB
...es.iOS.Tests/snapshots/ios-26/Issue33037NonShell_GridScrollView_AfterScroll.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+115 KB
...s.iOS.Tests/snapshots/ios-26/Issue33037NonShell_GridScrollView_BeforeScroll.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+80.7 KB
...estCases.iOS.Tests/snapshots/ios-26/Issue33037NonShell_ListView_AfterScroll.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.