Skip to content

Commit

Permalink
[iOS] Fix for the CollectionView to prevent duplicate groups or crash…
Browse files Browse the repository at this point in the history
…es when adding a new item to a group or when adding a new group. (#24873)

* [iOS] CollectionView issue fix.

* Added test case.

* Added snapshots.
  • Loading branch information
Tamilarasan-Paranthaman authored Sep 26, 2024
1 parent b953934 commit a5a6625
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,9 @@ internal CGSize GetReferenceSizeForheaderOrFooter(UICollectionView collectionVie
return CGSize.Empty;
}

if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsTvOSVersionAtLeast(11)))
{
// iOS 10 crashes if we try to dequeue a cell for measurement
// so we'll use an alternate method
return MeasureSupplementaryView(elementKind, section);
}

var cell = GetViewForSupplementaryElement(collectionView, elementKind,
NSIndexPath.FromItemSection(0, section)) as ItemsViewCell;

return cell.Measure();
// Dequeuing a supplementary view for measurement caused multiple instances of the header/footer to appear in the view.
// We now always use MeasureSupplementaryView, an alternate approach for calculating the size without dequeuing the view.
return MeasureSupplementaryView(elementKind, section);
}

internal void SetScrollAnimationEndedCallback(Action callback)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ bool NotLoadedYet()

void Add(NotifyCollectionChangedEventArgs args)
{
if (ReloadRequired())
if (NotLoadedYet())
{
Reload();
return;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue17969.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue17969"
xmlns:ns="clr-namespace:Maui.Controls.Sample.Issues"
x:DataType="ns:GroupedCollectionViewModel">

<Grid RowDefinitions="Auto,*">
<HorizontalStackLayout Grid.Row="0">
<Button Text="Add New Item" Clicked="OnAddClicked" AutomationId="addItem"/>
<Button Text="Add New Group" Clicked="OnResetClicked" AutomationId="addGroup"/>
</HorizontalStackLayout>
<CollectionView ItemsSource="{Binding Animals}"
AutomationId="collectionView"
Grid.Row="1"
IsGrouped="true">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="ns:Animal">
<Grid Padding="10">
<Label Text="{Binding Name}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.GroupHeaderTemplate>
<DataTemplate x:DataType="ns:AnimalGroup">
<Label Text="{Binding Name}" FontSize="18" FontAttributes="Bold"/>
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.GroupFooterTemplate>
<DataTemplate x:DataType="ns:AnimalGroup">
<Label Text="{Binding Count, StringFormat='Count: {0:D}'}"
Margin="0,0,0,10" />
</DataTemplate>
</CollectionView.GroupFooterTemplate>
</CollectionView>
</Grid>
</ContentPage>
187 changes: 187 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue17969.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.Maui.Controls;

namespace Maui.Controls.Sample.Issues
{

[Issue(IssueTracker.Github, 17969, "CollectionView duplicates group headers/footers when adding a new item to a group or crashes when adding a new group with empty view", PlatformAffected.iOS)]
public partial class Issue17969 : ContentPage
{
GroupedCollectionViewModel viewModel;
public Issue17969()
{
InitializeComponent();
viewModel = new GroupedCollectionViewModel();
BindingContext = viewModel;
}

private void OnAddClicked(object sender, EventArgs e)
{
viewModel.AddAnimal();
}

private void OnResetClicked(object sender, EventArgs e)
{
viewModel.Reset();
}

}

public class TestItem
{
public string Name { get; set; }
}
public class Animal
{
public string Name { get; set; }
}

public class AnimalGroup : ObservableCollection<Animal>
{
public string Name { get; private set; }

public AnimalGroup(string name, List<Animal> animals) : base(animals)
{
Name = name;
}
}

public class GroupedCollectionViewModel
{
public GroupedCollectionViewModel()
{
Init();

}

private void Init()
{
AnimalBaseList.Clear();
AnimalBaseList.Add(new AnimalGroup("Bears", new List<Animal>
{
new Animal
{
Name = "American Black Bear",
},
new Animal
{
Name = "Asian Black Bear",
},
new Animal
{
Name = "Brown Bear",
},
new Animal
{
Name = "Grizzly-Polar Bear Hybrid",
},
new Animal
{
Name = "Sloth Bear",
},
new Animal
{
Name = "Sun Bear",
},
new Animal
{
Name = "Polar Bear",
},
new Animal
{
Name = "Spectacled Bear",
},
new Animal
{
Name = "Short-faced Bear",
},
new Animal
{
Name = "California Grizzly Bear",
}
}));

AnimalBaseList.Add(new AnimalGroup("Cats", new List<Animal>
{
new Animal
{
Name = "Abyssinian",
},
new Animal
{
Name = "Arabian Mau",
},
new Animal
{
Name = "Bengal",
},
new Animal
{
Name = "Burmese",
},
new Animal
{
Name = "Cyprus",
},
new Animal
{
Name = "German Rex",
},
new Animal
{
Name = "Highlander",
},
new Animal
{
Name = "Peterbald",
},
new Animal
{
Name = "Scottish Fold",
},
new Animal
{
Name = "Sphynx",
}
}));

Animals.Add(new AnimalGroup(AnimalBaseList[0].Name, new List<Animal>()));

AddAnimal();

Animals.Add(new AnimalGroup(AnimalBaseList[1].Name, AnimalBaseList[1].ToList()));

}


public bool AddAnimal()
{
// Add animal from first group
var sourceGroup = AnimalBaseList.First();
var targetGroup = Animals.First();

if (sourceGroup.Count == 0)
return false;

var animal = sourceGroup.First();
targetGroup.Add(animal);
sourceGroup.Remove(animal);

return true;
}

public void Reset()
{
Animals.Clear();
Init();

}

private List<AnimalGroup> AnimalBaseList { get; set; } = new List<AnimalGroup>();

public ObservableCollection<AnimalGroup> Animals { get; private set; } = new ObservableCollection<AnimalGroup>();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#if !MACCATALYST
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue17969 : _IssuesUITest
{

public Issue17969(TestDevice device)
: base(device)
{ }

public override string Issue => "CollectionView duplicates group headers/footers when adding a new item to a group or crashes when adding a new group with empty view";

[Test]
[Category(UITestCategories.CollectionView)]
[FailsOnWindows]
public void CollectionViewDuplicateViewsWhenAddItemToGroup()
{
App.WaitForElement("collectionView");
App.Tap("addItem");
VerifyScreenshot();

}

[Test]
[Category(UITestCategories.CollectionView)]
[FailsOnWindows]
public void CollectionViewAddGroupWhenViewIsEmpty()
{
App.WaitForElement("collectionView");
App.Tap("addGroup");
VerifyScreenshot();
}
}
}
#endif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit a5a6625

Please sign in to comment.