Skip to content

Commit 778f8d2

Browse files
bhavanesh2001PureWeen
authored andcommitted
[iOS/MacCatalyst] Fix: Setting SelectedItem Programmatically and Then Immediately Setting ItemsSource to Null Causes a Crash (#29940)
* Re-evaluate indexpaths in PerformBatchUpdates * Add UI test * update test * bring back null check * add a check to see if ItemsSource is disposed
1 parent 3484739 commit 778f8d2

File tree

5 files changed

+140
-3
lines changed

5 files changed

+140
-3
lines changed

src/Controls/src/Core/Handlers/Items/iOS/SelectableItemsViewController.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public override void ItemDeselected(UICollectionView collectionView, NSIndexPath
4545
// Called by Forms to mark an item selected
4646
internal void SelectItem(object selectedItem)
4747
{
48-
if (ItemsView?.ItemsSource is null)
48+
if(ItemsView?.ItemsSource is not object originalSource)
4949
{
5050
return;
5151
}
@@ -57,6 +57,32 @@ internal void SelectItem(object selectedItem)
5757
// Ensure the selected index is updated after the collection view's items generation is completed
5858
CollectionView.PerformBatchUpdates(null, _ =>
5959
{
60+
// Ensure ItemsSource hasn't been disposed
61+
if (ItemsSource is EmptySource)
62+
{
63+
return;
64+
}
65+
66+
// Exit if the ItemsSource reference no longer matches the one captured at invocation.
67+
if (!ReferenceEquals(ItemsView.ItemsSource, originalSource))
68+
{
69+
return;
70+
}
71+
72+
// Recalculate the index for the selectedItem now that the collection may have changed.(Adding, deleting etc..)
73+
var updatedIndex = GetIndexForItem(selectedItem);
74+
if (updatedIndex.Section < 0 || updatedIndex.Item < 0)
75+
{
76+
return;
77+
}
78+
79+
// Retrieve the current item at that index and verify it still equals the intended selection.
80+
var liveItem = GetItemAtIndex(updatedIndex);
81+
if (!Equals(liveItem, selectedItem))
82+
{
83+
return;
84+
}
85+
6086
CollectionView.SelectItem(index, true, UICollectionViewScrollPosition.None);
6187
});
6288
}

src/Controls/src/Core/Handlers/Items2/iOS/SelectableItemsViewController2.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,44 @@ public override void ItemDeselected(UICollectionView collectionView, NSIndexPath
4545
// Called by Forms to mark an item selected
4646
internal void SelectItem(object selectedItem)
4747
{
48-
if (ItemsView?.ItemsSource is null)
48+
if(ItemsView?.ItemsSource is not object originalSource)
4949
{
5050
return;
5151
}
52-
52+
5353
var index = GetIndexForItem(selectedItem);
5454

5555
if (index.Section > -1 && index.Item > -1)
5656
{
5757
// Ensure the selected index is updated after the collection view's items generation is completed
5858
CollectionView.PerformBatchUpdates(null, _ =>
5959
{
60+
// Ensure ItemsSource hasn't been disposed
61+
if (ItemsSource is Items.EmptySource)
62+
{
63+
return;
64+
}
65+
66+
// Exit if the ItemsSource reference no longer matches the one captured at invocation.
67+
if (!ReferenceEquals(ItemsView.ItemsSource, originalSource))
68+
{
69+
return;
70+
}
71+
72+
// Recalculate the index for the selectedItem now that the collection may have changed.(Adding, deleting etc..)
73+
var updatedIndex = GetIndexForItem(selectedItem);
74+
if (updatedIndex.Section < 0 || updatedIndex.Item < 0)
75+
{
76+
return;
77+
}
78+
79+
// Retrieve the current item at that index and verify it still equals the intended selection.
80+
var liveItem = GetItemAtIndex(updatedIndex);
81+
if (!Equals(liveItem, selectedItem))
82+
{
83+
return;
84+
}
85+
6086
CollectionView.SelectItem(index, true, UICollectionViewScrollPosition.None);
6187
});
6288
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
x:Class="Maui.Controls.Sample.Issues.Issue29937"
5+
Title="Issue29937">
6+
<VerticalStackLayout
7+
Padding="30,0"
8+
Spacing="25">
9+
<Button AutomationId="MauiButton" Text="Set SelectedItem and make ItemSource = null" Clicked="Button_Clicked" />
10+
<CollectionView x:Name="cView" AutomationId="MauiCollectionView"
11+
ItemsSource="{Binding Items}"
12+
SelectionMode="Single">
13+
<CollectionView.ItemTemplate>
14+
<DataTemplate x:DataType="x:String">
15+
<VerticalStackLayout Padding="10" Margin="5">
16+
<Label Text="{Binding .}" HeightRequest="40" />
17+
</VerticalStackLayout>
18+
</DataTemplate>
19+
</CollectionView.ItemTemplate>
20+
</CollectionView>
21+
</VerticalStackLayout>
22+
</ContentPage>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Collections.ObjectModel;
2+
3+
namespace Maui.Controls.Sample.Issues;
4+
5+
[Issue(IssueTracker.Github, 29937, "[iOS/MacCatalyst] Setting SelectedItem Programmatically and Then Immediately Setting ItemsSource to Null Causes a Crash", PlatformAffected.iOS)]
6+
public partial class Issue29937 : ContentPage
7+
{
8+
private ObservableCollection<string> _items;
9+
10+
public ObservableCollection<string> Items
11+
{
12+
get => _items;
13+
set
14+
{
15+
_items = value;
16+
OnPropertyChanged(nameof(Items));
17+
}
18+
}
19+
public Issue29937()
20+
{
21+
InitializeComponent();
22+
Items = new ObservableCollection<string>
23+
{
24+
"Item 1",
25+
"Item 2",
26+
"Item 3",
27+
"Item 4",
28+
"Item 5"
29+
};
30+
BindingContext = this;
31+
}
32+
33+
void Button_Clicked(object sender, EventArgs e)
34+
{
35+
cView.SelectedItem = "Item 1";
36+
cView.ItemsSource = null;
37+
}
38+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using NUnit.Framework;
3+
using UITest.Appium;
4+
using UITest.Core;
5+
6+
namespace Microsoft.Maui.TestCases.Tests.Issues;
7+
8+
public class Issue29937 : _IssuesUITest
9+
{
10+
public Issue29937(TestDevice device) : base(device)
11+
{
12+
}
13+
14+
public override string Issue => "[iOS/MacCatalyst] Setting SelectedItem Programmatically and Then Immediately Setting ItemsSource to Null Causes a Crash";
15+
16+
[Test]
17+
[Category(UITestCategories.CollectionView)]
18+
public void SettingSelectedItemAndItemSourceShouldNotCrash()
19+
{
20+
App.WaitForElement("MauiButton");
21+
App.Tap("MauiButton");
22+
App.WaitForNoElement("MauiCollectionView");
23+
App.WaitForElement("MauiButton"); // If app doesn't crash, test passes.
24+
}
25+
}

0 commit comments

Comments
 (0)