Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] Avoid crash adding items to ObservableCollection #21613

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?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.Issue21374"
xmlns:viewModels="clr-namespace:Maui.Controls.Sample.Issues"
Title="Issue 21374">
<ContentPage.BindingContext>
<viewModels:Issue21374ViewModel />
</ContentPage.BindingContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button
Grid.Row="0"
AutomationId="WaitForStubControl"
Command="{Binding PopulateItemsCommand}"
Text="Populate items" />
<Grid
Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<CollectionView
Grid.Row="0"
ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout
Padding="8"
Orientation="Horizontal"
Spacing="16">
<Label
Text="{Binding Text}" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Label
Grid.Row="1"
Text="{Binding Success}"/>
</Grid>
</Grid>
</ContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Maui.Controls.Sample.Issues
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 21374, "Error when adding to ObservableCollection", PlatformAffected.iOS)]
public partial class Issue21374 : ContentPage
{
public Issue21374()
{
InitializeComponent();
}
}

public class Issue21374Model
{
public string Text { get; set; }
}

public class Issue21374ViewModel : INotifyPropertyChanged
{
string _success;
ObservableCollection<Issue21374Model> _items = new ObservableCollection<Issue21374Model>();

public ICommand PopulateItemsCommand => new Command(PopulateItems);

public string Success
{
get => _success;
set
{
_success = value;
OnPropertyChanged("Success");
}
}

public ObservableCollection<Issue21374Model> Items
{
get => _items;
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged("Items");
}
}
}

public event PropertyChangedEventHandler PropertyChanged;

void PopulateItems()
{
try
{
for (int j = 0; j < 10; j++)
{
Items.Add(new Issue21374Model { Text = $"Item {j + 1}" });
}

Success = "Success";
}
catch
{
Success = "Failed";
}
}

protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler changed = PropertyChanged;

if (changed == null)
{
return;
}

changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
10 changes: 4 additions & 6 deletions src/Controls/src/Core/Handlers/Items/iOS/ItemsViewLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -392,24 +392,22 @@ public override UICollectionViewLayoutInvalidationContext GetInvalidationContext
if (preferredAttributes.RepresentedElementKind != UICollectionElementKindSectionKey.Header
&& preferredAttributes.RepresentedElementKind != UICollectionElementKindSectionKey.Footer)
{
if (OperatingSystem.IsIOSVersionAtLeast(12) || OperatingSystem.IsTvOSVersionAtLeast(12))
{
return base.GetInvalidationContext(preferredAttributes, originalAttributes);
}

try
{
// We only have to do this on older iOS versions; sometimes when removing a cell that's right at the edge
// of the viewport we'll run into a race condition where the invalidation context will have the removed
// indexpath. And then things crash. So

var defaultContext = base.GetInvalidationContext(preferredAttributes, originalAttributes);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigating why null is returned in unexpected cases (the cell is visible).

Copy link
Contributor Author

@jsuarezruiz jsuarezruiz Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @PureWeen
Related to #21606

return defaultContext;
}
catch (ObjCRuntime.ObjCException ex) when (ex.Name == "NSRangeException")
{
Application.Current?.FindMauiContext()?.CreateLogger<ItemsViewLayout>()?.LogWarning(ex, "NSRangeException");
}
catch (ObjCRuntime.ObjCException ex) when (ex.Name == "NSInvalidArgumentException")
{
Application.Current?.FindMauiContext()?.CreateLogger<ItemsViewLayout>()?.LogWarning(ex, "NSInvalidArgumentException");
}

UICollectionViewFlowLayoutInvalidationContext context = new UICollectionViewFlowLayoutInvalidationContext();
return context;
Expand Down
31 changes: 31 additions & 0 deletions src/Controls/tests/UITests/Tests/Issues/Issue21374.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.AppiumTests.Issues
{
public class Issue21374 : _IssuesUITest
{
public Issue21374(TestDevice device) : base(device)
{
}

public override string Issue => "Error when adding to ObservableCollection";

[Test]
[Category(UITestCategories.CollectionView)]
public void AddingItemsToObservableCollectionNoCrash()
{
this.IgnoreIfPlatforms(new[]
{
TestDevice.Android,
TestDevice.Mac,
TestDevice.Windows
});

App.WaitForElement("WaitForStubControl");
App.Click("WaitForStubControl");
App.WaitForNoElement("Success");
}
}
}
Loading