--- /dev/null
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+
+#if UITEST
+ [Category(UITestCategories.CollectionView)]
+#endif
+
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 8647, "Crash on iOS when using DataTemplateSelector as a GroupingHeaderTemplate in a CollectionView", PlatformAffected.iOS)]
+ public class Issue8647 : TestContentPage
+ {
+ const string ButtonId = "ButtonId";
+ CollectionView _view;
+
+ protected override void Init()
+ {
+ var layout = new StackLayout { Margin = 50 };
+ layout.Children.Add(new Button { HorizontalOptions = LayoutOptions.Center, Text = "Fill Data", AutomationId = ButtonId, Command = new Command(_ => FillData()) });
+ _view = new CollectionView
+ {
+ IsGrouped = true,
+ GroupHeaderTemplate = new DummySelector(),
+ ItemTemplate = new DataTemplate(() =>
+ {
+ var item = new Label();
+ item.SetBinding(Label.TextProperty, nameof(Item.Value));
+ return item;
+ })
+ };
+ layout.Children.Add(_view);
+ Content = layout;
+ }
+
+ void FillData()
+ {
+ if (_view.ItemsSource == null)
+ {
+ _view.ItemsSource = new List<List<Item>>
+ {
+ new List<Item> { "1", "2", "3" },
+ new List<Item> { "4", "5" }
+ };
+ }
+ }
+
+ class DummySelector : DataTemplateSelector
+ {
+ protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
+ {
+
+ return new DataTemplate(() => new Label { Text = "GroupHeader" });
+ }
+ }
+
+ class Item
+ {
+ public string Value { get; set; }
+
+ public static implicit operator Item(string value) => new Item { Value = value };
+ }
+ }
+}
<Compile Include="$(MSBuildThisFileDirectory)Issue7249.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue8200.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue8417.xaml.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Issue8647.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue7510.cs" />
</ItemGroup>
<ItemGroup>
string DetermineViewReuseId(NSString elementKind)
{
- if (elementKind == UICollectionElementKindSectionKey.Header)
- {
- return DetermineViewReuseId(ItemsView.GroupHeaderTemplate);
- }
-
- return DetermineViewReuseId(ItemsView.GroupFooterTemplate);
+ return DetermineViewReuseId(elementKind == UICollectionElementKindSectionKey.Header
+ ? ItemsView.GroupHeaderTemplate
+ : ItemsView.GroupFooterTemplate);
}
string DetermineViewReuseId(DataTemplate template)
internal CGSize GetReferenceSizeForHeader(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
- if (!_isGrouped)
- {
- return CGSize.Empty;
- }
-
// Currently we explicitly measure all of the headers/footers
// Long-term, we might want to look at performance hints (similar to ItemSizingStrategy) for
// headers/footers (if the dev knows for sure they'll all the be the same size)
-
- var cell = GetViewForSupplementaryElement(collectionView, UICollectionElementKindSectionKey.Header,
- NSIndexPath.FromItemSection(0, section)) as ItemsViewCell;
-
- return cell.Measure();
+ return GetReferenceSizeForheaderOrFooter(collectionView, ItemsView.GroupHeaderTemplate, UICollectionElementKindSectionKey.Header, section);
}
internal CGSize GetReferenceSizeForFooter(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
- if (!_isGrouped)
+ return GetReferenceSizeForheaderOrFooter(collectionView, ItemsView.GroupFooterTemplate, UICollectionElementKindSectionKey.Footer, section);
+ }
+
+ internal CGSize GetReferenceSizeForheaderOrFooter(UICollectionView collectionView,DataTemplate template, NSString elementKind, nint section)
+ {
+ if (!_isGrouped || template == null)
{
return CGSize.Empty;
}
- var cell = GetViewForSupplementaryElement(collectionView, UICollectionElementKindSectionKey.Footer,
+ var cell = GetViewForSupplementaryElement(collectionView, elementKind,
NSIndexPath.FromItemSection(0, section)) as ItemsViewCell;
return cell.Measure();
}
+
internal void SetScrollAnimationEndedCallback(Action callback)
{
_scrollAnimationEndedCallback = callback;
{
cell.ContentSizeChanged -= CellContentSizeChanged;
- cell.Bind(ItemsView, ItemsSource[indexPath]);
+ cell.Bind(ItemsView.ItemTemplate, ItemsSource[indexPath], ItemsView);
cell.ContentSizeChanged += CellContentSizeChanged;
return preferredAttributes;
}
- public void Bind(ItemsView itemsView, object bindingContext)
- {
- var template = itemsView.ItemTemplate;
-
- // Run this through the extension method in case it's really a DataTemplateSelector
- template = template.SelectDataTemplate(bindingContext, itemsView);
-
- Bind(template, bindingContext, itemsView);
- }
-
public void Bind(DataTemplate template, object bindingContext, ItemsView itemsView)
{
var oldElement = VisualElementRenderer?.Element;
- if (template != _currentTemplate)
+ // Run this through the extension method in case it's really a DataTemplateSelector
+ var itemTemplate = template.SelectDataTemplate(bindingContext, itemsView);
+
+ if (itemTemplate != _currentTemplate)
{
// Remove the old view, if it exists
if (oldElement != null)
}
// Create the content and renderer for the view
- var view = template.CreateContent() as View;
+ var view = itemTemplate.CreateContent() as View;
var renderer = TemplateHelpers.CreateRenderer(view);
SetRenderer(renderer);
}
if (currentElement != null)
currentElement.BindingContext = bindingContext;
- if (template != _currentTemplate)
+ if (itemTemplate != _currentTemplate)
{
// And make the Element a "child" of the ItemsView
// We deliberately do this _after_ setting the binding context for the new element;
itemsView.AddLogicalChild(currentElement);
}
- _currentTemplate = template;
+ _currentTemplate = itemTemplate;
}
void SetRenderer(IVisualElementRenderer renderer)