diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9711.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9711.xaml
new file mode 100644
index 00000000000..42aae91118e
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9711.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9711.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9711.xaml.cs
new file mode 100644
index 00000000000..e76ec1678f1
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9711.xaml.cs
@@ -0,0 +1,128 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using System.Threading.Tasks;
+using CategoryAttribute = NUnit.Framework.CategoryAttribute;
+
+// Thanks to GitHub user [@Matmork](https://github.com/Matmork) for this reproducible test case.
+// https://github.com/xamarin/Xamarin.Forms/issues/9711#issuecomment-602520024
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 9711, "[Bug] iOS Failed to marshal the Objective-C object HeaderWrapperView", PlatformAffected.iOS)]
+ public partial class Issue9711 : TestContentPage
+ {
+ protected override void Init()
+ {
+#if APP
+ InitializeComponent();
+
+ List> groups = new List>();
+ for (int i = 0; i < 105; i++)
+ {
+ var group = new ListGroup { Title = $"Group{i}" };
+ for (int j = 0; j < 5; j++)
+ {
+ group.Add($"Group {i} Item {j}");
+ }
+
+ groups.Add(group);
+ }
+
+ TestListView.AutomationId = "9711TestListView";
+ TestListView.ItemsSource = groups;
+#endif
+ }
+
+ private void ViewCell_OnBindingContextChanged(object sender, EventArgs e)
+ {
+ if (sender is ViewCell cell && cell.BindingContext is ListGroup list)
+ {
+ list.Cell = cell;
+ }
+ }
+
+ private async void TapGestureRecognizer_OnTapped(object sender, EventArgs e)
+ {
+ if (sender is ContentView cnt && cnt.BindingContext is ListGroup list)
+ {
+ for (int i = 0; i <= 50; i++)
+ {
+ await Task.Delay(25);
+ list.IsExpanded = !list.IsExpanded;
+ }
+ }
+ }
+
+
+#if UITEST
+ [Category(UITestCategories.ListView)]
+ [Test]
+ public void TestTappingHeaderDoesNotCrash()
+ {
+ // Usually, tapping one header is sufficient to produce the exception.
+ // However, sometimes it takes two taps, and rarely, three. If the app
+ // crashes, one of the RunningApp queries will throw, failing the test.
+ Assert.DoesNotThrowAsync(async () =>
+ {
+ RunningApp.Tap(x => x.Marked("Group2"));
+ await Task.Delay(3000);
+ RunningApp.Tap(x => x.Marked("Group1"));
+ await Task.Delay(3000);
+ RunningApp.Tap(x => x.Marked("Group0"));
+ await Task.Delay(3000);
+ RunningApp.Query(x => x.Marked("9711TestListView"));
+ });
+ }
+#endif
+ }
+
+ [Preserve(AllMembers = true)]
+ public sealed class ListGroup : List, INotifyPropertyChanged, INotifyCollectionChanged
+ {
+ public string Title { get; set; }
+ public string AutomationId => Title;
+ private bool _isExpanded = true;
+
+ public bool IsExpanded
+ {
+ get => _isExpanded;
+ set
+ {
+ if (_isExpanded == value)
+ return;
+
+ if (Cell != null)
+ Cell.Height = value ? 75 : 40;
+
+ _isExpanded = value;
+ OnPropertyChanged();
+ OnCollectionChanged();
+ }
+ }
+
+ public ViewCell Cell { get; set; }
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private void OnCollectionChanged()
+ {
+ CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
index 39afa8d7b4b..2dafd30e42a 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
@@ -1362,6 +1362,10 @@
+
+ Issue9711.xaml
+ Code
+
@@ -1560,6 +1564,7 @@
Designer
MSBuild:UpdateDesignTimeXaml
+
@@ -2009,4 +2014,4 @@
MSBuild:UpdateDesignTimeXaml
-
\ No newline at end of file
+
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs
index 20b906b32be..09c8189a416 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs
@@ -4,7 +4,6 @@
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
-using System.Threading.Tasks;
using Foundation;
using UIKit;
using Xamarin.Forms.Internals;
@@ -1105,20 +1104,21 @@ public override nfloat GetHeightForHeader(UITableView tableView, nint section)
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
- UIView view = null;
-
if (!List.IsGroupingEnabled)
- return view;
+ return null;
var cell = TemplatedItemsView.TemplatedItems[(int)section];
if (cell.HasContextActions)
throw new NotSupportedException("Header cells do not support context actions");
+ const string reuseIdentifier = "HeaderWrapper";
+ var header = (HeaderWrapperView)tableView.DequeueReusableHeaderFooterView(reuseIdentifier) ?? new HeaderWrapperView(reuseIdentifier);
+ header.Cell = cell;
+
var renderer = (CellRenderer)Internals.Registrar.Registered.GetHandlerForObject(cell);
- view = new HeaderWrapperView { Cell = cell };
- view.AddSubview(renderer.GetCell(cell, null, tableView));
+ header.SetTableViewCell(renderer.GetCell(cell, null, tableView));
- return view;
+ return header;
}
public override void HeaderViewDisplayingEnded(UITableView tableView, UIView headerView, nint section)
@@ -1457,9 +1457,24 @@ void PreserveActivityIndicatorState(Element element)
}
}
- internal class HeaderWrapperView : UIView
+ class HeaderWrapperView : UITableViewHeaderFooterView
{
+ public HeaderWrapperView(string reuseIdentifier) : base((NSString)reuseIdentifier)
+ {
+ }
+
+ UITableViewCell _tableViewCell;
+
public Cell Cell { get; set; }
+
+ public void SetTableViewCell(UITableViewCell value)
+ {
+ if (ReferenceEquals(_tableViewCell, value)) return;
+ _tableViewCell?.RemoveFromSuperview();
+ _tableViewCell = value;
+ AddSubview(value);
+ }
+
public override void LayoutSubviews()
{
base.LayoutSubviews();