diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue36154.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue36154.cs new file mode 100644 index 000000000000..6d389e571fa8 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue36154.cs @@ -0,0 +1,126 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 36154, "WebView inside SwipeView no longer responds to swipe gestures on Android", PlatformAffected.Android)] +public class Issue36154 : ContentPage +{ + public Issue36154() + { + var directionLabel = new Label + { + AutomationId = "DirectionLabel", + Text = "Direction: — Offset: 0", + HorizontalOptions = LayoutOptions.Center, + Margin = new Thickness(4, 8), + FontSize = 13 + }; + + var resultLabel = new Label + { + AutomationId = "ResultLabel", + Text = "Swipe result will appear here", + HorizontalOptions = LayoutOptions.Center, + Margin = new Thickness(4, 0, 4, 12), + FontSize = 13, + TextColor = Colors.Gray + }; + + var webView = new WebView + { + AutomationId = "TheWebView", + HorizontalOptions = LayoutOptions.Fill, + VerticalOptions = LayoutOptions.Fill, + // Long scrollable page so vertical scroll can be verified + Source = new HtmlWebViewSource + { + Html = """ + + +

Issue 36154 – WebView scroll test

+

Scroll down to verify WebView vertical scrolling works inside SwipeView.

+

Then swipe left/right to reveal swipe items.

+

At the top edge, swipe down to reveal TopItems.

+

At the bottom edge, swipe up to reveal BottomItems.

+ """ + string.Concat(Enumerable.Range(1, 40).Select(i => + $"

Line {i}: Lorem ipsum dolor sit amet consectetur adipiscing elit.

")) + + "" + } + }; + + var swipeView = new SwipeView + { + AutomationId = "TheSwipeView", + Threshold = 80, + + LeftItems = new SwipeItems(new[] + { + new SwipeItem + { + AutomationId = "LeftItem", + Text = "◀ LEFT", + BackgroundColor = Colors.MediumSeaGreen, + Command = new Command(() => + { + resultLabel.Text = "LEFT item invoked"; + }) + } + }) + { Mode = SwipeMode.Execute, SwipeBehaviorOnInvoked = SwipeBehaviorOnInvoked.Close }, + + RightItems = new SwipeItems(new[] + { + new SwipeItem + { + AutomationId = "RightItem", + Text = "RIGHT ▶", + BackgroundColor = Colors.CornflowerBlue, + Command = new Command(() => + { + resultLabel.Text = "RIGHT invoked!"; + }) + } + }) + { Mode = SwipeMode.Execute, SwipeBehaviorOnInvoked = SwipeBehaviorOnInvoked.Close }, + + Content = webView + }; + + swipeView.SwipeStarted += (s, e) => + { + directionLabel.Text = $"Direction: {e.SwipeDirection} Offset: 0"; + }; + + swipeView.SwipeChanging += (s, e) => + directionLabel.Text = $"Direction: {e.SwipeDirection} Offset: {e.Offset:F0}"; + + swipeView.SwipeEnded += (s, e) => + { + directionLabel.Text = $"Direction: {e.SwipeDirection} Open: {e.IsOpen}"; + }; + + Content = new Grid + { + RowDefinitions = + [ + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Star }, + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Auto }, + ], + Children = + { + new Label + { + Text = "Swipe on WebView · scroll mid-page · swipe at edges", + HorizontalOptions = LayoutOptions.Center, + Margin = new Thickness(8), + FontSize = 13, + FontAttributes = FontAttributes.Bold + }.Row(0), + + swipeView.Row(1), + directionLabel.Row(2), + resultLabel.Row(3) + } + }; + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue36154.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue36154.cs new file mode 100644 index 000000000000..734ccabb9a09 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue36154.cs @@ -0,0 +1,29 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue36154 : _IssuesUITest +{ + public Issue36154(TestDevice testDevice) : base(testDevice) + { + } + + public override string Issue => "WebView inside SwipeView no longer responds to swipe gestures on Android"; + + // Verify that swiping left on the WebView reveals the Right swipe items + [Test] + [Category(UITestCategories.SwipeView)] + public void Issue36154SwipeViewShouldRevealItems() + { + var rect = App.WaitForElement("TheSwipeView").GetRect(); + var centerX = rect.X + rect.Width / 2; + var centerY = rect.Y + rect.Height / 2; + + // Swipe left (finger moves left) → reveals RightItems + App.DragCoordinates(centerX, centerY, centerX - 200, centerY); + + Assert.That(App.WaitForElement("ResultLabel").GetText(), Is.EqualTo("RIGHT invoked!")); + } +} diff --git a/src/Core/src/Platform/Android/MauiSwipeView.cs b/src/Core/src/Platform/Android/MauiSwipeView.cs index f79e5ceeecd7..5462a84480a5 100644 --- a/src/Core/src/Platform/Android/MauiSwipeView.cs +++ b/src/Core/src/Platform/Android/MauiSwipeView.cs @@ -133,6 +133,11 @@ bool ShouldInterceptScrollChildrenTouch(SwipeDirection swipeDirection) if (_contentView is null || _initialPoint is null) return false; + if (_contentView is AWebView contentWebView) + { + return ShouldInterceptWebViewTouch(contentWebView, swipeDirection); + } + var viewGroup = _contentView as ViewGroup; if (viewGroup is not null) @@ -166,6 +171,17 @@ bool ShouldInterceptScrollChildrenTouch(SwipeDirection swipeDirection) return true; } + // Determines whether the SwipeView should intercept touch events when the content is a WebView, based on the WebView's scroll position and the swipe direction. + static bool ShouldInterceptWebViewTouch(AWebView webView, SwipeDirection swipeDirection) => + swipeDirection switch + { + SwipeDirection.Right => !webView.CanScrollHorizontally(-1), // at left edge + SwipeDirection.Left => !webView.CanScrollHorizontally(1), // at right edge + SwipeDirection.Down => !webView.CanScrollVertically(-1), // at top + SwipeDirection.Up => !webView.CanScrollVertically(1), // at bottom + _ => true, + }; + static bool ShouldInterceptScrollChildrenTouch(ViewGroup scrollView, bool isHorizontal) { AView? scrollViewContent = scrollView.GetChildAt(0); diff --git a/src/Core/src/Platform/Android/MauiWebView.cs b/src/Core/src/Platform/Android/MauiWebView.cs index c1abe5859adb..ffcfb08f836e 100644 --- a/src/Core/src/Platform/Android/MauiWebView.cs +++ b/src/Core/src/Platform/Android/MauiWebView.cs @@ -13,6 +13,7 @@ public class MauiWebView : WebView, IWebViewDelegate readonly WebViewHandler _handler; readonly Rect _clipRect; + bool _hasSwipeViewParent; volatile bool _detachPending; // True after the first layout pass where exactly one dimension is positive and the other is zero. @@ -60,6 +61,8 @@ protected override void OnAttachedToWindow() // Re-evaluate ClipBounds when re-parented (e.g., wrapped in WrapperView for shadow) UpdateClipBounds(Width, Height); + _hasSwipeViewParent = ((View)this).GetParentOfType() is not null; + if (RefreshViewWebViewScrollCapture.IsInsideMauiSwipeRefreshLayout(this)) { RefreshViewWebViewScrollCapture.Attach(this); @@ -99,6 +102,7 @@ protected override void OnDetachedFromWindow() } base.OnDetachedFromWindow(); + _hasSwipeViewParent = false; } void UpdateClipBounds(int width, int height) @@ -151,7 +155,13 @@ public override bool OnTouchEvent(MotionEvent? e) { case MotionEventActions.Down: case MotionEventActions.Move: - Parent?.RequestDisallowInterceptTouchEvent(true); + // Do not request disallow intercept when inside a SwipeView — that would set + // FLAG_DISALLOW_INTERCEPT on the SwipeView and prevent it from detecting + // swipe gestures + if (!_hasSwipeViewParent) + { + Parent?.RequestDisallowInterceptTouchEvent(true); + } break; case MotionEventActions.Up: