CollectionView is broken on IOS when items have different sizes and ItemSizingStrategy="MeasureAllItems". It means that we have no way to use items with different sizes
All items size set correctly like after scrolling (screenshot 2)
Some of items overlap each other
posted in Steps to Reproduce
no
no
Grid or FlexLayout (where Grid or Flex is child of DataTemplate) with 3 labels located in 3 rows
Can you post the markup of your DataTemplate?
(2 of them can be invisible in some cases which means that CollectionView cell height will be different from time to time).
How are you handling the visibility of the rows? Are you setting IsVisible via DataBinding? Or some other way?
@hartez, Visibility is handling by text existence - if there is no text in row there will be no label, so no height in "Auto" height row.
<CollectionView Style="{StaticResource ChatsCollectionViewStyle}" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Path=SelectedItem}" EmptyView="{x:Static p:ResourceManager.PlaceholderEmptyChat}"
EmptyViewTemplate="{StaticResource EmptyCollectionViewTemplate}" SelectionMode="Single">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type cm:ChatDetailViewModel}">
<Grid AutomationId="{x:Static t:TestResources.ChatsListItemCell}" Style="{StaticResource ChatItemGridStyle}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="63" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="1" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding Chat, Converter={StaticResource ChatTitlePreviewConverter}}" Style="{StaticResource TitleLabelStyle}" StyleClass="BoldWhenUnread" />
<Label Grid.Row="0" Grid.Column="1" HorizontalTextAlignment="End" HorizontalOptions="End" Text="{Binding Path=Chat.Updated, Converter={StaticResource DateTimeToDayConverterWithCustomDateFormat}}" Style="{StaticResource DateLabelStyle}" StyleClass="BoldWhenUnread" />
<Label Grid.Row="1" Grid.Column="0" Margin="8,2,0,0" Text="{Binding Chat, Converter={StaticResource ChatParticipantsPreviewConverter}}" Style="{StaticResource SmallerItemLabel}" StyleClass="BoldWhenUnread" />
<Label Grid.Row="2" Grid.Column="0" Margin="8,2,0,8" Text="{Binding Chat, Converter={StaticResource ChatLastMessagePreviewConverter}}" Style="{StaticResource SmallerItemLabel}" StyleClass="ItalicBoldWhenUnread" />
<Frame HorizontalOptions="End" Grid.Row="2" Grid.Column="1" Margin="8,2,8,8" WidthRequest="20" IsVisible="{Binding Chat.Unread}" Style="{StaticResource BadgeFrameStyle}">
<Label Text="{Binding Chat.UnreadCount, Converter={StaticResource NumberToStringLimited}}" MaxLines="1" Style="{StaticResource BadgeLabelStyle}" />
</Frame>
<BoxView Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource SeparatorStyle}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Also it is reproducible in a different types of Root elements for DataTemplate.
When I tested it I just created FlexLayout instead of grid and put there 3 labels from sample of code above (3 labels as 3 rows inside FlexLayout) - and result was the same - rows were cutted when cells height is different.
I can confirm this issue. In my case, I have a DataTemplateSelector which selects two very different DataTemplates based on a property in my ItemViewModel. It often happens when the smaller Template is used in the lower third of the screen.
Is there any workaround we could implement for the time being?
I can confirm as well. Reason number 70 why CollectionView should still be experimental.
Had the same problem. Because of the different Labels' heights their parent containers were overlapping. As a workaround I've set Labels' TextType proberty to HTML, and wrapped their values. Something like: $"<p style='color:#8f92a1;font-size:16px;font-family: Arial;display:inline-block;'>{Text}</p>";
. Not good, but seem to work..
I've the same problem.
Is there any workaround or resolution date?
I had a similar problem with dynamically resizing items not being correctly resized and solved it by calling this every time after I change an item:
collectionView.ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem;
collectionView.ItemSizingStrategy = ItemSizingStrategy.MeasureAllItems;
This seems to remeasure all items.. Don't forget to call it on a main thread.
After a lot of attempt, I achieved to have a solution that is working on iOS with a CollecitonView Grouped containing different DataTemplate of different Sizes (using DataTemplateSelector) with hundred of items.
If it can help :
In my opinion, there's two main causes with the current implementation
1) Self Sizing. The implementation used by ItemSizingStrategy.MeasureAllItems is based on self sizing. It seems that, there's still some weird behavior using this approach... I think that's a better practice to define a fixed size for each possible DataTemplate (should be ok for a lot of scenario). Inspired by https://github.com/xamarin/Xamarin.Forms/issues/10842#issuecomment-688709149, I've added a mechanism that provides the expected cell size for each item type in order to avoid the use of EstimatedSize things if possible...:
public override CGSize GetSizeForItem(UICollectionView collectionView, UICollectionViewLayout layout, NSIndexPath indexPath)
{
//use CollectionView ItemsSource to get ViewModel type associated to indexPath
var itemType = GetCellItemViewModel(indexPath);
if (itemType == null)
{
return ItemsViewLayout.EstimatedItemSize;
}
//Get Height from your Shared Assembly
var cellHeight = ProduitItemViewModelsCellAssociations.GetCellHeight(itemType);
var cellWidth = (double)collectionView.Frame.Width;
//Special treatments if using GridViewLayout ?
if (_itemsViewLayout is GridViewLayoutAdvanced gridViewLayoutAdvanced)
{
cellWidth /= gridViewLayoutAdvanced.Span;
cellWidth -= gridViewLayoutAdvanced.HorizontalItemSpacing;
}
return new CGSize(cellWidth, cellHeight);
}
2) ReuseIdentifier mechanism. Foreach VerticalCell, the same ReuseIdentifier is used: see the RegisterViewTypes method https://github.com/xamarin/Xamarin.Forms/blob/f35ae07a0a8471d255f7a1ebdd51499e10e0a4cb/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs. I've added a mechanism that creates a ReuseIdentifier dedicated for each DataTemplate (=> ItemViewModel type).
//Custom Controller
private class GroupableItemsViewAdvancedController<TItemsView> : GroupableItemsViewController<TItemsView>
where TItemsView : GroupableItemsView
{
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var cellItemViewModel = GetCellItemViewModel(indexPath);
if (cellItemViewModel == null)
{
return base.GetCell(collectionView, indexPath);
}
var defaultCellReuseIdentifier = DetermineCellReuseId();
var cellReuseIdentifier = defaultCellReuseIdentifier + cellItemViewModel.GetType().ToString() ?? string.Empty;
//Ensure that each type of item (particular DataTemplate) has its proper identifier
//to avoid recycling problems and follow iOS best practice
if (!_customCellsIdentidiers.Contains(cellReuseIdentifier))
{
//Get VerticalCell internal type if not already get
_defaultCellInstanceType = _defaultCellInstanceType ?? CollectionView.DequeueReusableCell(defaultCellReuseIdentifier, indexPath).GetType();
CollectionView.RegisterClassForCell(_defaultCellInstanceType, cellReuseIdentifier);
_customCellsIdentidiers.Add(cellReuseIdentifier);
}
var nativeCell = collectionView.DequeueReusableCell(cellReuseIdentifier, indexPath) as UICollectionViewCell;
//Code from base.GetCell
switch (nativeCell)
{
case DefaultCell defaultCell:
UpdateDefaultCell(defaultCell, indexPath);
break;
case TemplatedCell templatedCell:
UpdateTemplatedCell(templatedCell, indexPath);
break;
}
return nativeCell;
}
}
Feel free to comment/enhance my solution :)
Most helpful comment
I can confirm as well. Reason number 70 why CollectionView should still be experimental.