diff --git a/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs b/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs index adebfc16e999..8064a98c8ce1 100644 --- a/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs +++ b/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs @@ -524,6 +524,28 @@ protected virtual void LayoutPropertyChanged(object sender, PropertyChangedEvent } } + public override bool OnTouchEvent(MotionEvent e) + { + // If ItemsView is disabled, don't handle touch events + if (ItemsView?.IsEnabled == false) + { + return false; + } + + return base.OnTouchEvent(e); + } + + public override bool OnInterceptTouchEvent(MotionEvent e) + { + // If ItemsView is disabled, intercept all touch events to prevent interactions + if (ItemsView?.IsEnabled == false) + { + return true; + } + + return base.OnInterceptTouchEvent(e); + } + protected override void OnLayout(bool changed, int l, int t, int r, int b) { base.OnLayout(changed, l, t, r, b); diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt index 7dc5c58110bf..6b13b646e109 100644 --- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +~override Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView.OnInterceptTouchEvent(Android.Views.MotionEvent e) -> bool +~override Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView.OnTouchEvent(Android.Views.MotionEvent e) -> bool \ No newline at end of file diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue19771.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue19771.cs new file mode 100644 index 000000000000..2c244f8b1d0e --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue19771.cs @@ -0,0 +1,93 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 19771, "CollectionView IsEnabled=false allows touch interactions on iOS and Android", PlatformAffected.iOS | PlatformAffected.Android)] +public class Issue19771 : ContentPage +{ + const string DisableButton = "DisableButton"; + const string TestCollectionView = "TestCollectionView"; + const string InteractionCountLabel = "InteractionCountLabel"; + const string StatusLabel = "StatusLabel"; + + private int _interactionCount = 0; + private Label _interactionLabel; + private Label _statusLabel; + private CollectionView _collectionView; + + public Issue19771() + { + var items = new List { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" }; + + _statusLabel = new Label + { + AutomationId = StatusLabel, + Text = "CollectionView is ENABLED", + FontSize = 16, + FontAttributes = FontAttributes.Bold, + HorizontalOptions = LayoutOptions.Center, + Margin = new Thickness(10) + }; + + _interactionLabel = new Label + { + AutomationId = InteractionCountLabel, + Text = "Interaction Count: 0", + FontSize = 14, + HorizontalOptions = LayoutOptions.Center, + Margin = new Thickness(5) + }; + + var disableButton = new Button + { + AutomationId = DisableButton, + Text = "IsEnabled=False", + HorizontalOptions = LayoutOptions.Center, + Margin = new Thickness(10) + }; + disableButton.Clicked += (s, e) => SetEnabled(false); + + _collectionView = new CollectionView + { + AutomationId = TestCollectionView, + Background = Colors.AliceBlue, + ItemsSource = items, + SelectionMode = SelectionMode.Single, + HeightRequest = 300, + ItemTemplate = new DataTemplate(() => + { + var grid = new Grid { Padding = 10 }; + var label = new Label { TextColor = Colors.Black }; + label.SetBinding(Label.TextProperty, "."); + grid.Children.Add(label); + return grid; + }) + }; + + _collectionView.SelectionChanged += (s, e) => + { + _interactionCount++; + _interactionLabel.Text = $"Interaction Count: {_interactionCount}"; + }; + + Content = new StackLayout + { + Children = + { + _statusLabel, + _interactionLabel, + new StackLayout + { + Orientation = StackOrientation.Horizontal, + HorizontalOptions = LayoutOptions.Center, + Children = { disableButton } + }, + _collectionView + } + }; + } + + private void SetEnabled(bool isEnabled) + { + _collectionView.IsEnabled = isEnabled; + _statusLabel.Text = isEnabled ? "CollectionView is ENABLED" : "CollectionView is DISABLED"; + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue19771.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue19771.cs new file mode 100644 index 000000000000..57441bc87c90 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue19771.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue19771 : _IssuesUITest +{ + public Issue19771(TestDevice device) : base(device) + { + } + + public override string Issue => "CollectionView IsEnabled=false allows touch interactions on iOS and Android"; + + [Test] + [Category(UITestCategories.CollectionView)] + public void CollectionViewIsEnabledFalsePreventsInteractions() + { + App.WaitForElement("Item 1"); + App.Tap("Item 1"); + App.WaitForElement("DisableButton"); + App.Tap("DisableButton"); + App.WaitForElement("Item 3"); + App.Tap("Item 3"); + var text = App.WaitForElement("InteractionCountLabel").GetText(); + Assert.That(text, Is.EqualTo("Interaction Count: 1")); + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/ViewExtensions.cs b/src/Core/src/Platform/iOS/ViewExtensions.cs index 2bc47d31920c..ac90294e0602 100644 --- a/src/Core/src/Platform/iOS/ViewExtensions.cs +++ b/src/Core/src/Platform/iOS/ViewExtensions.cs @@ -17,10 +17,16 @@ public static partial class ViewExtensions public static void UpdateIsEnabled(this UIView platformView, IView view) { - if (platformView is not UIControl uiControl) - return; - - uiControl.Enabled = view.IsEnabled; + if (platformView is UIControl uiControl) + { + // UIControl has native Enabled property with visual feedback + uiControl.Enabled = view.IsEnabled; + } + else + { + // Non-UIControl views (like UICollectionView) only get interaction disable + platformView.UserInteractionEnabled = view.IsEnabled; + } } public static void Focus(this UIView platformView, FocusRequest request)