Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ public MauiCarouselRecyclerView(Context context, Func<IItemsLayout> getItemsLayo

public override bool OnInterceptTouchEvent(MotionEvent ev)
{
if (!IsSwipeEnabled)
if (!IsSwipeEnabled || !Enabled)
{
return false;
}

return base.OnInterceptTouchEvent(ev);
}
Expand Down
10 changes: 10 additions & 0 deletions src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -629,5 +629,15 @@ void RemoveScrollListener()
ClearOnScrollListeners();
RecyclerViewScrollListener = null;
}

public override bool OnInterceptTouchEvent(MotionEvent ev)
{
if (!Enabled)
{
return false;
}

return base.OnInterceptTouchEvent(ev);
}
}
}
3 changes: 3 additions & 0 deletions src/Controls/src/Core/Handlers/Items/CarouselViewHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public CarouselViewHandler(PropertyMapper mapper = null) : base(mapper ?? Mapper
{
#if TIZEN || ANDROID
[Controls.CarouselView.ItemsLayoutProperty.PropertyName] = MapItemsLayout,
#endif
#if IOS || MACCATALYST
[Controls.VisualElement.IsEnabledProperty.PropertyName] = MapIsEnabled,
#endif
[Controls.CarouselView.IsSwipeEnabledProperty.PropertyName] = MapIsSwipeEnabled,
[Controls.CarouselView.PeekAreaInsetsProperty.PropertyName] = MapPeekAreaInsets,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ protected override void ScrollToRequested(object sender, ScrollToRequestEventArg
}
}

// TODO: Change the modifier to public in .NET 11.
internal static void MapIsEnabled(CarouselViewHandler handler, CarouselView carouselView)
{
handler.Controller?.CollectionView?.UpdateIsEnabled(carouselView);
}

public static void MapIsSwipeEnabled(CarouselViewHandler handler, CarouselView carouselView)
{
handler.Controller.CollectionView.ScrollEnabled = carouselView.IsSwipeEnabled;
Expand Down
5 changes: 4 additions & 1 deletion src/Controls/src/Core/Handlers/Items/ItemsViewHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ public ItemsViewHandler(PropertyMapper mapper = null) : base(mapper ?? ItemsView
[Controls.ItemsView.EmptyViewTemplateProperty.PropertyName] = MapEmptyViewTemplate,
[Controls.ItemsView.FlowDirectionProperty.PropertyName] = MapFlowDirection,
[Controls.ItemsView.IsVisibleProperty.PropertyName] = MapIsVisible,
[Controls.ItemsView.ItemsUpdatingScrollModeProperty.PropertyName] = MapItemsUpdatingScrollMode
[Controls.ItemsView.ItemsUpdatingScrollModeProperty.PropertyName] = MapItemsUpdatingScrollMode,
#if IOS || MACCATALYST
[Controls.VisualElement.IsEnabledProperty.PropertyName] = MapIsEnabled,
#endif
};
}
}
6 changes: 6 additions & 0 deletions src/Controls/src/Core/Handlers/Items/ItemsViewHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ public static void MapItemsUpdatingScrollMode(ItemsViewHandler<TItemsView> handl
handler._layout.ItemsUpdatingScrollMode = itemsView.ItemsUpdatingScrollMode;
}

// TODO: Change the modifier to public in .NET 11.
internal static void MapIsEnabled(ItemsViewHandler<TItemsView> handler, ItemsView itemsView)
{
handler.Controller?.CollectionView?.UpdateIsEnabled(itemsView);
}

protected virtual void UpdateLayout()
{
_layout = SelectLayout();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public CarouselViewHandler2(PropertyMapper mapper = null) : base(mapper ?? Mappe

public static PropertyMapper<CarouselView, CarouselViewHandler2> Mapper = new(ItemsViewMapper)
{

[Controls.VisualElement.IsEnabledProperty.PropertyName] = MapIsEnabled,
[Controls.CarouselView.IsSwipeEnabledProperty.PropertyName] = MapIsSwipeEnabled,
[Controls.CarouselView.PeekAreaInsetsProperty.PropertyName] = MapPeekAreaInsets,
[Controls.CarouselView.IsBounceEnabledProperty.PropertyName] = MapIsBounceEnabled,
Expand Down Expand Up @@ -69,6 +69,12 @@ protected override void ScrollToRequested(object sender, ScrollToRequestEventArg
}
}

// TODO: Change the modifier to public in .NET 11.
internal static void MapIsEnabled(CarouselViewHandler2 handler, CarouselView carouselView)
{
handler.Controller?.CollectionView?.UpdateIsEnabled(carouselView);
}

public static void MapIsSwipeEnabled(CarouselViewHandler2 handler, CarouselView carouselView)
{
handler.Controller.CollectionView.ScrollEnabled = carouselView.IsSwipeEnabled;
Expand Down
13 changes: 10 additions & 3 deletions src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ public ItemsViewHandler2(PropertyMapper mapper = null) : base(mapper ?? ItemsVie
[Controls.ItemsView.EmptyViewTemplateProperty.PropertyName] = MapEmptyViewTemplate,
[Controls.ItemsView.FlowDirectionProperty.PropertyName] = MapFlowDirection,
[Controls.ItemsView.IsVisibleProperty.PropertyName] = MapIsVisible,
[Controls.ItemsView.ItemsUpdatingScrollModeProperty.PropertyName] = MapItemsUpdatingScrollMode
};

[Controls.ItemsView.ItemsUpdatingScrollModeProperty.PropertyName] = MapItemsUpdatingScrollMode,
[Controls.VisualElement.IsEnabledProperty.PropertyName] = MapIsEnabled
};

UICollectionViewLayout _layout;

protected override void DisconnectHandler(UIView platformView)
Expand Down Expand Up @@ -115,6 +116,12 @@ public static void MapIsVisible(ItemsViewHandler2<TItemsView> handler, ItemsView
handler.Controller?.UpdateVisibility();
}

// TODO: Change the modifier to public in .NET 11.
internal static void MapIsEnabled(ItemsViewHandler2<TItemsView> handler, ItemsView itemsView)
{
handler.Controller?.CollectionView?.UpdateIsEnabled(itemsView);
}

public static void MapItemsUpdatingScrollMode(ItemsViewHandler2<TItemsView> handler, ItemsView itemsView)
{
if (handler.ItemsView is StructuredItemsView structuredItemsView && structuredItemsView.ItemsLayout is ItemsLayout itemsLayout)
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
~override Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource>.OnInterceptTouchEvent(Android.Views.MotionEvent ev) -> bool
65 changes: 65 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue32791.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 32791, "Setting the IsEnabled property of the CarouselView to false does not prevent swiping through items", PlatformAffected.Android | PlatformAffected.iOS )]
public class Issue32791 : ContentPage
{
CarouselView _carouselView;
Label _statusLabel;
public Issue32791()
{
_carouselView = new CarouselView
{
AutomationId = "DisabledCarouselView",
HeightRequest = 300,
IsEnabled = false,
Loop = false,
ItemsSource = new string[] { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" },
ItemTemplate = new DataTemplate(() =>
{
Label label = new Label
{
FontSize = 32,
BackgroundColor = Colors.LightGray,
Padding = 20,
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Fill
};
label.SetBinding(Label.TextProperty, ".");

return label;
})
};

_carouselView.CurrentItemChanged += OnCarouselViewCurrentItemChanged;

_statusLabel = new Label
{
AutomationId = "Issue32791StatusLabel",
Text = "Success",
};

Grid grid = new Grid
{
Padding = 20,
RowSpacing = 20,
RowDefinitions =
{
new RowDefinition { Height = new GridLength(300) },
new RowDefinition { Height = GridLength.Auto }
}
};

grid.Add(_carouselView, 0, 0);
grid.Add(_statusLabel, 0, 1);

Content = grid;
}

void OnCarouselViewCurrentItemChanged(object sender, CurrentItemChangedEventArgs e)
{
if (e.CurrentItem?.ToString() != "Item 1")
{
_statusLabel.Text = "Failure";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue32791 : _IssuesUITest
{
public Issue32791(TestDevice device) : base(device)
{
}

public override string Issue => "Setting the IsEnabled property of the CarouselView to false does not prevent swiping through items";

[Test]
[Category(UITestCategories.CarouselView)]
public void VerifyCarouselViewPreventsSwipingWhenDisabled()
{
App.WaitForElement("DisabledCarouselView");
App.ScrollRight("DisabledCarouselView");

#if MACCATALYST
// MacCatalyst: Wait for item transition to complete after scroll gesture
Thread.Sleep(1000);
#endif
var statusText = App.WaitForElement("Issue32791StatusLabel").GetText();
Assert.That(statusText, Is.EqualTo("Success"));
Comment on lines +22 to +27
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Platform-conditional compilation with Thread.Sleep in the test can hide test failures on MacCatalyst. If the sleep is truly necessary for MacCatalyst, consider documenting why, or better yet, use a proper wait mechanism.

According to the UI testing guidelines, tests should use App.WaitForElement or similar explicit waits rather than arbitrary sleeps. If there's a specific element state you're waiting for on MacCatalyst, wait for that explicitly.

Suggested change
#if MACCATALYST
// MacCatalyst: Wait for item transition to complete after scroll gesture
Thread.Sleep(1000);
#endif
var statusText = App.WaitForElement("Issue32791StatusLabel").GetText();
Assert.That(statusText, Is.EqualTo("Success"));
// Wait for the status label to display "Success" after scroll gesture
App.WaitFor(() =>
{
var label = App.FindElement("Issue32791StatusLabel");
return label != null && label.GetText() == "Success";
}, timeout: TimeSpan.FromSeconds(5));
var statusText = App.FindElement("Issue32791StatusLabel").GetText();
Assert.That(statusText, Is.EqualTo("Success"));

Copilot uses AI. Check for mistakes.
}
}
8 changes: 8 additions & 0 deletions src/Core/src/Platform/iOS/CollectionViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ namespace Microsoft.Maui.Platform
{
public static class CollectionViewExtensions
{
// TODO: Change the modifier to public in .NET 11.
internal static void UpdateIsEnabled(this UICollectionView collectionView, IView view)
{
// UICollectionView inherits from UIScrollView (not UIControl), so we set UserInteractionEnabled
// to properly disable user interactions like scrolling and swiping based on IsEnabled
collectionView.UserInteractionEnabled = view.IsEnabled;
}

public static void UpdateVerticalScrollBarVisibility(this UICollectionView collectionView, ScrollBarVisibility scrollBarVisibility)
{
collectionView.ShowsVerticalScrollIndicator = scrollBarVisibility == ScrollBarVisibility.Always || scrollBarVisibility == ScrollBarVisibility.Default;
Expand Down
Loading