-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Fix for First Item in CollectionView Overlaps in FlyoutPage.Flyout on iOS #29265
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
Changes from all commits
fb7effa
c09d4b8
dc11ec0
76993bb
dd6e383
2edc82e
4f7d489
3578319
3ec65e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -365,6 +365,24 @@ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) | |
| void LayoutChildren(bool animated) | ||
| { | ||
| var frame = Element.Bounds.ToCGRect(); | ||
|
|
||
| // Apply safe area insets to the FlyoutPage container if UseSafeArea is enabled. | ||
| // This ensures the Flyout content (e.g., CollectionView) renders below the status bar/notch | ||
| // on iOS devices with notches (iPhone X and newer). Without this adjustment, the container | ||
| // would start at Y=0, causing content to overlap with the status bar. | ||
| // https://github.com/dotnet/maui/issues/29170 | ||
| if (Element is FlyoutPage flyoutPage && flyoutPage is ISafeAreaView sav && | ||
| !sav.IgnoreSafeArea && OperatingSystem.IsIOSVersionAtLeast(11)) | ||
| { | ||
| var safeAreaTopInset = View.SafeAreaInsets.Top; | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated the PR with the recommended code optimization by extracting |
||
| if (safeAreaTopInset > 0) | ||
| { | ||
| frame.Y = safeAreaTopInset; | ||
| frame.Height -= safeAreaTopInset; | ||
| } | ||
| } | ||
|
Comment on lines
+374
to
+384
|
||
|
|
||
| var flyoutFrame = frame; | ||
| nfloat opacity = 1; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| namespace Maui.Controls.Sample.Issues; | ||
|
|
||
| using System; | ||
| using Microsoft.Maui; | ||
| using Microsoft.Maui.Controls; | ||
| using Microsoft.Maui.Controls.Xaml; | ||
| using Microsoft.Maui.Graphics; | ||
| using Microsoft.Maui.Controls.PlatformConfiguration; | ||
| using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific; | ||
|
|
||
| [XamlCompilation(XamlCompilationOptions.Compile)] | ||
| [Issue(IssueTracker.Github, 29170, "First Item in CollectionView Overlaps in FlyoutPage.Flyout on iOS", PlatformAffected.iOS)] | ||
| public partial class Issue29170 : Microsoft.Maui.Controls.FlyoutPage | ||
| { | ||
| public Issue29170() | ||
| { | ||
| Flyout = new ContentPage | ||
| { | ||
| Title = "Menu", | ||
| Content = CreateCollectionView("CollectionViewFlyout") | ||
| }; | ||
|
|
||
| var toggleButton = new Button | ||
| { | ||
| Text = "Open Flyout Menu", | ||
| FontSize = 24, | ||
| AutomationId = "FlyoutButton", | ||
| Margin = 10, | ||
| WidthRequest = 220, | ||
| HeightRequest = 50, | ||
| HorizontalOptions = LayoutOptions.Center, | ||
| BackgroundColor = Colors.Blue | ||
| }; | ||
| toggleButton.Clicked += ToggleFlyoutMenu; | ||
|
|
||
| var detailCollectionView = CreateCollectionView("CollectionViewDetail"); | ||
| detailCollectionView.Footer = toggleButton; | ||
|
|
||
| Detail = new ContentPage | ||
| { | ||
| Content = detailCollectionView | ||
| }; | ||
|
|
||
| this.On<iOS>().SetUseSafeArea(true); | ||
| } | ||
|
|
||
| private CollectionView CreateCollectionView(string automationId) | ||
| { | ||
| return new CollectionView | ||
| { | ||
| ItemsSource = new[] { "Item 1", "Item 2", "Item 3", "Item 4" }, | ||
| AutomationId = automationId, | ||
| Margin = 10, | ||
| ItemTemplate = new DataTemplate(() => | ||
| { | ||
| var titleLabel = new Label | ||
| { | ||
| FontSize = 32, | ||
| LineBreakMode = LineBreakMode.TailTruncation | ||
| }; | ||
| titleLabel.SetBinding(Label.TextProperty, new Binding(".")); | ||
|
|
||
| var subHeaderLabel = new Label | ||
| { | ||
| FontSize = 16, | ||
| Opacity = 0.66, | ||
| LineBreakMode = LineBreakMode.TailTruncation, | ||
| Text = "subheader" | ||
| }; | ||
|
|
||
| return new VerticalStackLayout | ||
| { | ||
| Padding = new Thickness(5), | ||
| Children = { titleLabel, subHeaderLabel } | ||
| }; | ||
| }) | ||
| }; | ||
| } | ||
|
|
||
| private void ToggleFlyoutMenu(object sender, EventArgs e) => IsPresented = !IsPresented; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| #if IOS //iOS only support the SetUseSafeArea | ||
| using NUnit.Framework; | ||
| using UITest.Appium; | ||
| using UITest.Core; | ||
|
|
||
| namespace Microsoft.Maui.TestCases.Tests.Issues; | ||
|
|
||
| internal class Issue29170 : _IssuesUITest | ||
| { | ||
| public Issue29170(TestDevice device) : base(device) { } | ||
|
|
||
| public override string Issue => "First Item in CollectionView Overlaps in FlyoutPage.Flyout on iOS"; | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.FlyoutPage)] | ||
| public void CollectionViewFirstItemShouldNotOverlapWithSafeAreaInFlyoutMenu() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test is failing on iOS:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jsuarezruiz , now the test case has been updated with proper assert conditions that correctly verify safe area respecting behavior on iOS devices. |
||
| { | ||
| // Use 44 (status bar height) as conservative minimum instead of device-specific value | ||
| // This prevents test flakiness across different simulators and iOS versions | ||
| const int MinimumSafeAreaHeight = 44; | ||
|
|
||
| Assert.That(App.WaitForElement("CollectionViewDetail").GetRect().Y, Is.GreaterThanOrEqualTo(MinimumSafeAreaHeight), | ||
| $"CollectionView Y position should be at least {MinimumSafeAreaHeight} to avoid overlapping with safe area"); | ||
| App.WaitForElement("FlyoutButton").Tap(); | ||
| Assert.That(App.WaitForElement("CollectionViewFlyout").GetRect().Y, Is.GreaterThanOrEqualTo(MinimumSafeAreaHeight), | ||
| $"CollectionView Y position should be at least {MinimumSafeAreaHeight} to avoid overlapping with safe area"); | ||
| } | ||
| } | ||
| #endif | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition
Element is FlyoutPage flyoutPageis redundant. SinceFlyoutPageis a property that already castsElementtoFlyoutPage, andFlyoutPageinherits fromPagewhich implementsISafeAreaView, you can simplify this to:This eliminates the unnecessary pattern matching and uses the existing
FlyoutPageproperty.