-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[Catalyst] Fix: CollectionView Crash #29801
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
[Catalyst] Fix: CollectionView Crash #29801
Conversation
|
Hey there @@Tamilarasan-Paranthaman! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds defensive null and handle checks around NSIndexPath usage in both iOS and Catalyst SelectableItemsViewController implementations to prevent crashes when an index path has been disposed.
- Added guards in
ItemSelectedandItemDeselectedmethods to return early ifindexPathis null or its handle is zero. - Added the same guard in the internal
SelectItemmethod before accessingindex.Sectionandindex.Item.
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Controls/src/Core/Handlers/Items2/iOS/SelectableItemsViewController2.cs | Added null/handle checks in selection callbacks and SelectItem |
| src/Controls/src/Core/Handlers/Items/iOS/SelectableItemsViewController.cs | Added null/handle checks in selection callbacks and SelectItem |
Comments suppressed due to low confidence (1)
src/Controls/src/Core/Handlers/Items2/iOS/SelectableItemsViewController2.cs:28
- Add a unit or UI test to simulate a null or disposed
NSIndexPathscenario to verify these guards prevent crashes as intended.
if (indexPath is null || indexPath.Handle == IntPtr.Zero)
| // _Only_ called if the user initiates the selection change; will not be called for programmatic selection | ||
| public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath) | ||
| { | ||
| if (indexPath is null || indexPath.Handle == IntPtr.Zero) |
Copilot
AI
Jun 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same null-and-handle check is duplicated across methods—consider extracting this into a private helper (e.g., bool IsValidIndexPath(NSIndexPath path)) to reduce duplication and ease future maintenance.
| if (indexPath is null || indexPath.Handle == IntPtr.Zero) | |
| if (!IsValidIndexPath(indexPath)) |
| // _Only_ called if the user initiates the selection change; will not be called for programmatic selection | ||
| public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath) | ||
| { | ||
| if (indexPath is null || indexPath.Handle == IntPtr.Zero) |
Copilot
AI
Jun 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar guard logic is repeated here—extract into a shared helper method to keep the code DRY and make future updates simpler.
| if (indexPath is null || indexPath.Handle == IntPtr.Zero) | |
| if (!IsValidIndexPath(indexPath)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion IsValidIndexPath can be used for this check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think IsValidIndexPath is only on ListViewRenderer?
One thing I'm wondering is if we should re-evalute the indexes when they are used inside of PerformBatchUpdates
like in this code
CollectionView.PerformBatchUpdates(null, _ =>
{
CollectionView.SelectItem(index, true, UICollectionViewScrollPosition.None);
CollectionView.CellForItem(index)?.UpdateSelectedAccessibility(true);
});if the index should be retrieved a second time and if it's changed at all then we don't process the update
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PureWeen The case you mentioned could occur if the ItemsSource is updated immediately after setting the SelectedItem property, as shown in the example below. In such scenarios, during the PerformBatchUpdates call, the CollectionView items may be modified, and the index might no longer exist in the collection. In this case, we do not proceed with updating the collection.
However, the originally reported issue is unrelated to that scenario. As seen in the stack trace, the issue originates from the ItemSelected override method, which is triggered when the user taps to select an item. This indicates that the crash is likely occurring during user interaction. On the other hand, the SelectItem method you referenced is only invoked during programmatic selection, which is not the case here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sheiksyedm, will the changes in this PR also fix the issue caused by the 'ItemsSource updated immediately' problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bhavanesh2001 No, it will not fix the ItemsSource updated problem. This fix may only address the ObjectDisposedException observed in the reported stack trace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/dotnet/maui/pull/29801/files#r2125125141
I'm wondering if we should re-evaluate the index when the performbatchupdate method is called and if that's the better overall fix here?
Or maybe we should change the ordering?
CollectionView.SelectItem(index, true, UICollectionViewScrollPosition.None);
Is going to trigger a behavior change reaction in the code
Would it make sense to have it like this instead
CollectionView.CellForItem(index)?.UpdateSelectedAccessibility(true);
CollectionView.SelectItem(index, true, UICollectionViewScrollPosition.None);There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think IsValidIndexPath is only on ListViewRenderer?
One thing I'm wondering is if we should re-evalute the indexes when they are used inside of PerformBatchUpdates
like in this code
CollectionView.PerformBatchUpdates(null, _ => { CollectionView.SelectItem(index, true, UICollectionViewScrollPosition.None); CollectionView.CellForItem(index)?.UpdateSelectedAccessibility(true); });if the index should be retrieved a second time and if it's changed at all then we don't process the update
We have this one https://github.com/dotnet/maui/blob/main/src/Compatibility/Core/src/iOS/CollectionView/IndexPathHelpers.cs#L36
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well thats Compatibility , this is the one I think we use on the CV
| public static bool IsIndexPathValid(this IItemsViewSource source, NSIndexPath indexPath) |
|
This seems to me that is hiding the issue, for some reason we are trying to deselect and disposed cell, I wonder if we can trying to figure out better what is calling it |
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
@rmarinho, as mentioned in the PR description, I was unfortunately unable to reproduce the reported issue. This change is intended as a temporary solution based solely on the call stack details, rather than a proper fix. |
|
|
||
| // Called by Forms to mark an item selected | ||
| internal void SelectItem(object selectedItem) | ||
| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To extend on @PureWeen’s comment here: #29801 (comment)
we can be more cautious when performing PerformBatchUpdates(...) by adding guards to ensure the selection logic applies to the current and valid ItemsSource.
Something like this might help avoid re-selecting stale or unintended items if the ItemsSource has changed between scheduling and executing the batch update:
internal void SelectItem(object selectedItem)
{
var originalSource = ItemsView.ItemsSource;
var index = GetIndexForItem(selectedItem);
if (index.Section > -1 && index.Item > -1)
{
// Ensure the selected index is updated after the collection view's items generation is completed
CollectionView.PerformBatchUpdates(null, _ =>
{
// Exit if the ItemsSource reference no longer matches the one captured at invocation.
if (!ReferenceEquals(ItemsView.ItemsSource, originalSource))
{
return;
}
// Recalculate the index for the selectedItem now that the collection may have changed.(Adding, deleting etc..)
var updatedIndex = GetIndexForItem(selectedItem);
if (updatedIndex.Section < 0 || updatedIndex.Item < 0)
{
return;
}
// Retrieve the current item at that index and verify it still equals the intended selection.
var liveItem = GetItemAtIndex(updatedIndex);
if (!Equals(liveItem, selectedItem))
{
return;
}
CollectionView.SelectItem(index, true, UICollectionViewScrollPosition.None);
CollectionView.CellForItem(index)?.UpdateSelectedAccessibility(true);
});
}
}|
@Tamilarasan-Paranthaman Could you rebase to fix the conflicts? Thanks in advance. |
@jsuarezruiz, we reverted that implementation in PR #29827, so this PR can be closed. |
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description of Change
A crash was reported on Mac Catalyst with the following exception:
ObjectDisposed_Generic for Foundation.NSIndexPath, occurring during selection in a CollectionView.Based on the call stack, the crash originates from SelectableItemsViewController.ItemSelected, which attempts to use a disposed NSIndexPath. Although I was unable to reproduce the issue despite testing scenarios such as:
I have provided a defensive fix by adding a null and handle check before invoking FormsSelectItem, FormsDeselectItem and SelectItem.
This change is expected to prevent the crash by defensively checking for a disposed NSIndexPath, without affecting expected selection behavior.
Issues Fixed
Fixes #29791