Skip to content
Closed
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 @@ -43,13 +43,10 @@ public void Recycle(ItemsView itemsView)

itemsView.RemoveLogicalChild(View);

// Disconnect and clear the handler via ItemContentView.Recycle(), which calls
// DisconnectHandlers() before releasing Content. Reset _selectedTemplate so the
// next Bind() call always goes through the templateChanging path and recreates
// the handler (since we just disconnected it).
// Disconnect the current handler and release the platform content. We keep the
// existing View/_selectedTemplate references so the next Bind() can decide whether
// it needs to re-realize the same templated view or create a new one.
_itemContentView.Recycle();
View = null; // clear reference to the disconnected view
_selectedTemplate = null; // force templateChanging=true on next Bind() to recreate the view
}

public void Bind(object itemBindingContext, ItemsView itemsView,
Expand Down Expand Up @@ -90,8 +87,16 @@ public void Bind(object itemBindingContext, ItemsView itemsView,

if (!templateChanging)
{
// Same template, new data
// Same template — update binding context. If the handler was disconnected
// during recycle, re-realize the existing view to reconnect platform content
// without losing any runtime property changes (e.g. dynamic HeightRequest).
View.BindingContext = itemBindingContext;

if (View?.Handler is null)
{
PropertyPropagationExtensions.PropagatePropertyChanged(null, View, itemsView);
_itemContentView.RealizeContent(View, itemsView);
}
}

itemsView.AddLogicalChild(View);
Expand Down
136 changes: 136 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue34783.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System.Collections.ObjectModel;

namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 34783, "CollectionView Dynamic item sizing - After dragging the scrollbar all images return to their original size", PlatformAffected.Android)]
public class Issue34783 : ContentPage
{
public Issue34783()
{
BindingContext = new Issue34783ViewModel();

var grid = new Grid
{
Margin = new Thickness(20),
RowDefinitions = new RowDefinitionCollection
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Star }
}
};

var instructions = new StackLayout
{
Children =
{
new Label { Text = "1.Confirm that the CollectionView below is populated with monkeys and can be scrolled." },
new Label { Text = "2.The test passes if tapping each image dynamically changes its size." }
}
};

var tapLabel = new Label { Text = "Tap each image to dynamically change its size." };

var itemTemplate = new DataTemplate(() =>
{
var innerGrid = new Grid { Padding = new Thickness(10) };
innerGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
innerGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
innerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
innerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });

var image = new Image
{
Aspect = Aspect.AspectFill,
HeightRequest = 60,
WidthRequest = 60
};
image.SetBinding(Image.SourceProperty, new Binding("ImageUrl"));
image.SetBinding(Image.AutomationIdProperty, new Binding("Name"));

var tapGesture = new TapGestureRecognizer();
tapGesture.Tapped += OnImageTapped;
image.GestureRecognizers.Add(tapGesture);

Grid.SetRowSpan(image, 2);

var nameLabel = new Label { FontAttributes = FontAttributes.Bold };
nameLabel.SetBinding(Label.TextProperty, new Binding("Name"));
Grid.SetColumn(nameLabel, 1);

var locationLabel = new Label
{
FontAttributes = FontAttributes.Italic,
VerticalOptions = LayoutOptions.End
};
locationLabel.SetBinding(Label.TextProperty, new Binding("Location"));
Grid.SetRow(locationLabel, 1);
Grid.SetColumn(locationLabel, 1);

innerGrid.Children.Add(image);
innerGrid.Children.Add(nameLabel);
innerGrid.Children.Add(locationLabel);

return innerGrid;
});

var collectionView = new CollectionView
{
AutomationId = "Issue34783CollectionView",
ItemTemplate = itemTemplate
};
collectionView.SetBinding(CollectionView.ItemsSourceProperty, new Binding("Monkeys"));

Grid.SetRow(tapLabel, 1);
Grid.SetRow(collectionView, 2);

grid.Children.Add(instructions);
grid.Children.Add(tapLabel);
grid.Children.Add(collectionView);

Content = grid;
}

void OnImageTapped(object sender, EventArgs e)
{
if (sender is Image image)
image.HeightRequest = image.WidthRequest = image.HeightRequest.Equals(60) ? 100 : 60;
}
}

public class Issue34783Monkey
{
public string Name { get; set; }
public string Location { get; set; }
public string Details { get; set; }
public string ImageUrl { get; set; }
}

public class Issue34783ViewModel
{
public ObservableCollection<Issue34783Monkey> Monkeys { get; set; }

public Issue34783ViewModel()
{
Monkeys = new ObservableCollection<Issue34783Monkey>
{
new Issue34783Monkey { Name = "Baboon", Location = "Africa & Asia", ImageUrl = "papio.jpg" },
new Issue34783Monkey { Name = "Capuchin Monkey", Location = "Central & South America", ImageUrl = "capuchin.jpg" },
new Issue34783Monkey { Name = "Blue Monkey", Location = "Central and East Africa", ImageUrl = "bluemonkey.jpg" },
new Issue34783Monkey { Name = "Squirrel Monkey", Location = "Central & South America", ImageUrl = "saimiri.jpg" },
new Issue34783Monkey { Name = "Golden Lion Tamarin", Location = "Brazil", ImageUrl = "golden.jpg" },
new Issue34783Monkey { Name = "Howler Monkey", Location = "South America", ImageUrl = "alouatta.jpg" },
new Issue34783Monkey { Name = "Japanese Macaque", Location = "Japan", ImageUrl = "papio.jpg" },
new Issue34783Monkey { Name = "Mandrill", Location = "Central Africa", ImageUrl = "capuchin.jpg" },
new Issue34783Monkey { Name = "Proboscis Monkey", Location = "Borneo", ImageUrl = "bluemonkey.jpg" },
new Issue34783Monkey { Name = "Red-shanked Douc", Location = "Vietnam, Laos", ImageUrl = "saimiri.jpg" },
new Issue34783Monkey { Name = "Gray-shanked Douc", Location = "Vietnam", ImageUrl = "golden.jpg" },
new Issue34783Monkey { Name = "Snub-nosed Monkey", Location = "China", ImageUrl = "alouatta.jpg" },
new Issue34783Monkey { Name = "Black Snub-nosed", Location = "China", ImageUrl = "papio.jpg" },
new Issue34783Monkey { Name = "Tonkin Monkey", Location = "Vietnam", ImageUrl = "capuchin.jpg" },
new Issue34783Monkey { Name = "Thomas Langur", Location = "Indonesia", ImageUrl = "bluemonkey.jpg" },
new Issue34783Monkey { Name = "Purple-faced Langur", Location = "Sri Lanka", ImageUrl = "saimiri.jpg" },
new Issue34783Monkey { Name = "Gelada", Location = "Ethiopia", ImageUrl = "golden.jpg" },
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#if TEST_FAILS_ON_CATALYST // Issue34783 is flaky on MacCatalyst due to non-deterministic scroll-recycle timing.
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue34783 : _IssuesUITest
{
public override string Issue => "CollectionView Dynamic item sizing - After dragging the scrollbar all images return to their original size";

public Issue34783(TestDevice device) : base(device) { }

[Test]
[Category(UITestCategories.CollectionView)]
public void ImagesKeepSizeAfterScrolling()
{
// Wait for the CollectionView to load; first item "Baboon" image is used as the anchor
App.WaitForElement("Baboon");

// Record the initial rendered height of the first image (should be ~60)
var initialRect = App.WaitForElement("Baboon").GetRect();

// Tap the first image to enlarge it (60 → 100) using coordinates to reliably
// hit the Image element (which has a TapGestureRecognizer, not a Button click)
App.TapCoordinates(initialRect.X + initialRect.Width / 2, initialRect.Y + initialRect.Height / 2);

// Confirm the image has grown
var enlargedRect = App.WaitForElement("Baboon").GetRect();
Assert.That(enlargedRect.Height, Is.GreaterThan(initialRect.Height),
"Image should be enlarged after tap");

// Scroll down to push the first item off screen (simulates dragging the scrollbar,
// which triggers RecyclerView item recycling on Android)
App.ScrollDown("Issue34783CollectionView", ScrollStrategy.Gesture, 0.9);

// Scroll back up to bring Baboon back into view
App.ScrollUp("Issue34783CollectionView", ScrollStrategy.Gesture, 0.9);
App.ScrollUp("Issue34783CollectionView", ScrollStrategy.Gesture, 0.9);

// Verify the image is still at the enlarged size and was not reset to the original
App.WaitForElement("Baboon");
var afterScrollRect = App.WaitForElement("Baboon").GetRect();
Assert.That(afterScrollRect.Height, Is.GreaterThan(initialRect.Height),
"Image should keep its enlarged size after scrolling; resetting to original size is the bug");
}
}
#endif
Loading