Skip to content

Commit

Permalink
fix(listview): [iOS] Fix container reuse by template
Browse files Browse the repository at this point in the history
When ItemTemplateSelector is set, ensure list only reuses containers for items with the same template. This was previously failing because the native reuse identifier was being set degenerately.
  • Loading branch information
davidjohnoliver committed Oct 15, 2021
1 parent deda184 commit 868446f
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 3 deletions.
Expand Up @@ -22,6 +22,11 @@ internal partial class CounterGrid : Grid
public static int GlobalMeasureCount { get; private set; }
public static int GlobalArrangeCount { get; private set; }

/// <summary>
/// How many times has this instance been data-bound?
/// </summary>
public int LocalBindCount { get; private set; }

public int LocalMeasureCount { get; private set; }
public int LocalArrangeCount { get; private set; }

Expand All @@ -43,6 +48,7 @@ private void On_DataContextChanged(DependencyObject sender, DataContextChangedEv
if (args.NewValue != null)
{
BindCount++;
LocalBindCount++;
WasUpdated?.Invoke();
}
}
Expand All @@ -63,6 +69,9 @@ protected override Size ArrangeOverride(Size finalSize)
return base.ArrangeOverride(finalSize);
}

/// <summary>
/// Reset static counters.
/// </summary>
public static void Reset()
{
CreationCount = 0;
Expand Down
Expand Up @@ -59,6 +59,10 @@ public partial class Given_ListViewBase
private DataTemplate SelectableItemTemplateB => _testsResources["SelectableItemTemplateB"] as DataTemplate;
private DataTemplate SelectableItemTemplateC => _testsResources["SelectableItemTemplateC"] as DataTemplate;

private DataTemplate RedSelectableTemplate => _testsResources["RedSelectableTemplate"] as DataTemplate;
private DataTemplate GreenSelectableTemplate => _testsResources["GreenSelectableTemplate"] as DataTemplate;
private DataTemplate BeigeSelectableTemplate => _testsResources["BeigeSelectableTemplate"] as DataTemplate;

[TestInitialize]
public void Init()
{
Expand Down Expand Up @@ -1443,6 +1447,79 @@ void InsertAnItem()
}
}

[TestMethod]
public async Task When_ItemTemplate_Selector_Correct_Reuse()
{
var selector = new KeyedTemplateSelector<ItemColor>(o => (o as ItemColorViewModel)?.ItemType ?? ItemColor.None)
{
Templates =
{
{ItemColor.Red, RedSelectableTemplate },
{ItemColor.Green, GreenSelectableTemplate},
{ItemColor.Beige, BeigeSelectableTemplate}
}
};

var source = new List<ItemColorViewModel>();
int itemNo = 0;
void AddItem(ItemColor itemType)
{
itemNo++;
source.Add(new ItemColorViewModel { ItemType = itemType, ItemIndex = itemNo });
}

AddItem(ItemColor.Red);
AddItem(ItemColor.Green);

for (int i = 0; i < 10; i++)
{
AddItem(ItemColor.Beige);
}

AddItem(ItemColor.Green);

var SUT = new ListView
{
Width = 180,
Height = 320,
ItemsSource = source,
ItemTemplateSelector = selector,
ItemsPanel = NoCacheItemsStackPanel,
ItemContainerStyle = NoSpaceContainerStyle
};

WindowHelper.WindowContent = SUT;
await WindowHelper.WaitForLoaded(SUT);

var redLeader = SUT.ContainerFromIndex(0) as ListViewItem;
var greenLeader = SUT.ContainerFromIndex(1) as ListViewItem;
var redGrid = redLeader.FindFirstChild<CounterGrid>();
var greenGrid = greenLeader.FindFirstChild<CounterGrid>();

var sv = SUT.FindFirstChild<ScrollViewer>();

sv.ChangeView(null, 100, null, disableAnimation: true);
await Task.Delay(20);

var redCount1 = redGrid.LocalBindCount;
var greenCount1 = greenGrid.LocalBindCount;

for (int i = 300; i < 1000; i += 300)
{
sv.ChangeView(null, i, null, disableAnimation: true);
await Task.Delay(20);
}


var redCount2 = redGrid.LocalBindCount;
var greenCount2 = greenGrid.LocalBindCount;

Assert.AreEqual(redCount1, redCount2); // Red template should not have been rebound
Assert.AreEqual(greenCount1 + 1, greenCount2); // Green template should be reused once for final item
}



private bool ApproxEquals(double value1, double value2) => Math.Abs(value1 - value2) <= 2;

private class When_Removed_From_Tree_And_Selection_TwoWay_Bound_DataContext : System.ComponentModel.INotifyPropertyChanged
Expand Down Expand Up @@ -1556,4 +1633,58 @@ protected override DataTemplate SelectTemplateCore(object item)
return template;
}
}

public class KeyedTemplateSelector<T> : DataTemplateSelector
{
private readonly Func<object, T> _keySelector;

public KeyedTemplateSelector(Func<object, T> keySelector = null)
{
this._keySelector = keySelector;
}

public IDictionary<T, DataTemplate> Templates { get; } = new Dictionary<T, DataTemplate>();

protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) => SelectTemplateCore(item); // On UWP only this overload is called when eg Button.ContentTemplateSelector is set

protected override DataTemplate SelectTemplateCore(object item)
{
if (item == null)
{
return null;
}

T itemT;
if (_keySelector != null)
{
itemT = _keySelector(item);
}
else if (item is T)
{
itemT = (T)item;
}
else
{
return null;
}

var template = Templates.UnoGetValueOrDefault(itemT);
return template;
}
}

public enum ItemColor
{
None,
Red,
Green,
Beige
}

public class ItemColorViewModel
{
public ItemColor ItemType { get; set; }
public int ItemIndex { get; set; }
public string DisplayString => $"Item {ItemIndex}";
}
}
Expand Up @@ -3,9 +3,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls"
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
xmlns:xamarin="http://uno.ui/xamarin"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
mc:Ignorable="d xamarin">
<Style x:Key="BasicListViewContainerStyle"
TargetType="ListViewItem">
<Setter Property="Template">
Expand All @@ -29,7 +30,8 @@
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
ContentTemplate="{TemplateBinding ContentTemplate}"
xamarin:ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}" />
</ControlTemplate>
</Setter.Value>
</Setter>
Expand Down Expand Up @@ -215,4 +217,41 @@
TextWrapping="Wrap" />
</Border>
</DataTemplate>

<DataTemplate x:Name="RedSelectableTemplate">
<Border Background="Red"
BorderBrush="DarkRed"
BorderThickness="2"
CornerRadius="5"
Height="95"
MinWidth="100">
<local:CounterGrid>
<TextBlock Text="{Binding DisplayString}" />
</local:CounterGrid>
</Border>
</DataTemplate>
<DataTemplate x:Name="GreenSelectableTemplate">
<Border Background="Green"
BorderBrush="DarkGreen"
BorderThickness="2"
CornerRadius="5"
Height="125"
MinWidth="100">
<local:CounterGrid>
<TextBlock Text="{Binding DisplayString}" />
</local:CounterGrid>
</Border>
</DataTemplate>
<DataTemplate x:Name="BeigeSelectableTemplate">
<Border Background="Beige"
BorderBrush="SaddleBrown"
BorderThickness="2"
CornerRadius="5"
Height="54"
MinWidth="100">
<local:CounterGrid>
<TextBlock Text="{Binding DisplayString}" />
</local:CounterGrid>
</Border>
</DataTemplate>
</ResourceDictionary>
Expand Up @@ -642,7 +642,7 @@ private NSString GetReusableCellIdentifier(NSIndexPath indexPath)

if (!_templateCells.TryGetValue(template ?? _nullDataTemplateKey, out identifier))
{
identifier = new NSString(_templateCache.Count.ToString(CultureInfo.InvariantCulture));
identifier = new NSString(_templateCells.Count.ToString(CultureInfo.InvariantCulture));
_templateCells[template ?? _nullDataTemplateKey] = identifier;

Owner.RegisterClassForCell(typeof(ListViewBaseInternalContainer), identifier);
Expand Down

0 comments on commit 868446f

Please sign in to comment.