[iOS] ListView should not explicitly remove subviews coming from renderers (#1307)
authorSamantha Houts <samhouts@users.noreply.github.com>
Wed, 29 Nov 2017 19:21:18 +0000 (11:21 -0800)
committerRui Marinho <me@ruimarinho.net>
Wed, 29 Nov 2017 19:21:18 +0000 (19:21 +0000)
* Add repro for 60563

* [iOS} ListView should not explicitly remove subviews coming from renderers

Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla60563.cs [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs

diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla60563.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla60563.cs
new file mode 100644 (file)
index 0000000..19ea1bb
--- /dev/null
@@ -0,0 +1,92 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using System.Collections.Generic;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+       [Preserve(AllMembers = true)]
+       [Issue(IssueTracker.Bugzilla, 60563, "ActivityIndicator in ListView causes SIGSEGV crash in iOS 8", PlatformAffected.iOS)]
+       public class Bugzilla60563 : TestNavigationPage
+       {
+               const string btnGoToList = "btnGoToList";
+               const string spinner = "spinner";
+
+               protected override void Init()
+               {
+                       Navigation.PushAsync(new NavigationPage(new StartPage()));
+               }
+
+               [Preserve(AllMembers = true)]
+               class ListPage : ContentPage
+               {
+                       public ListPage()
+                       {
+                               Title = "List";
+                               Content = new ListView
+                               {
+                                       HasUnevenRows = false,
+                                       RowHeight = 50,
+                                       ItemTemplate = new DataTemplate(() => { return new SpinnerViewCell(); }),
+                                       ItemsSource = new List<int> { 1, 2, 3, 4, 5 },
+                               };
+                       }
+               }
+
+               [Preserve(AllMembers = true)]
+               class SpinnerViewCell : ViewCell
+               {
+                       public SpinnerViewCell()
+                       {
+                               var indicator = new ActivityIndicator
+                               {
+                                       IsRunning = true,
+                                       AutomationId = spinner
+                               };
+                               var layout = new RelativeLayout();
+                               layout.Children.Add(indicator, x: () => 0, y: () => 0);
+                               View = indicator;
+                       }
+               }
+
+               [Preserve(AllMembers = true)]
+               class StartPage : ContentPage
+               {
+                       public StartPage()
+                       {
+                               var button = new Button
+                               {
+                                       Text = "Go To List",
+                                       BackgroundColor = Color.Beige,
+                                       HeightRequest = 40,
+                                       WidthRequest = 100,
+                                       VerticalOptions = LayoutOptions.Center,
+                                       HorizontalOptions = LayoutOptions.Center,
+                                       AutomationId = btnGoToList
+                               };
+                               button.Clicked += (sender, e) => Navigation.PushAsync(new ListPage());
+
+                               Title = "Home";
+                               Content = new StackLayout { Children = { new Label { Text = "Click the button to go to a ListView with an ActivityIndicator, then go back to this page. If the app does not crash, this test has passed." }, button } };
+                       }
+               }
+
+#if UITEST && __IOS__
+               [Test]
+               public void Bugzilla60563Test()
+               {
+                       RunningApp.WaitForElement(q => q.Marked(btnGoToList));
+                       RunningApp.Tap(q => q.Marked(btnGoToList));
+                       RunningApp.WaitForElement(q => q.Marked(spinner));
+                       RunningApp.Back();
+                       RunningApp.WaitForElement(q => q.Marked(btnGoToList));
+                       RunningApp.Tap(q => q.Marked(btnGoToList));
+                       RunningApp.WaitForElement(q => q.Marked(spinner));
+               }
+#endif
+       }
+}
\ No newline at end of file
index aa0bffc..0b2e8c7 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)Bugzilla59863_0.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Bugzilla59863_1.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Bugzilla59863_2.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Bugzilla60563.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)ButtonBackgroundColorTest.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)CarouselAsync.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Bugzilla34561.cs" />
index 1c3a725..0c1911c 100644 (file)
@@ -102,10 +102,19 @@ namespace Xamarin.Forms.Platform.iOS
 
                void DisposeSubviews(UIView view)
                {
-                       foreach (UIView subView in view.Subviews)
-                               DisposeSubviews(subView);
+                       var ver = view as IVisualElementRenderer;
+
+                       if (ver == null)
+                       {
+                               // VisualElementRenderers should implement their own dispose methods that will appropriately dispose and remove their child views.
+                               // Attempting to do this work twice could cause a SIGSEGV (only observed in iOS8), so don't do this work here.
+                               // Non-renderer views, such as separator lines, etc., can be removed here.
+                               foreach (UIView subView in view.Subviews)
+                                       DisposeSubviews(subView);
+
+                               view.RemoveFromSuperview();
+                       }
 
-                       view.RemoveFromSuperview();
                        view.Dispose();
                }