[iOS] GestureManager.SetupGestureManager crashes with InvalidOperationException when handler.PlatformView is null after disconnect (→ SIGABRT)
- Dominant language
- C#
- Stars
- 23.3k
- Forks
- 2k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 290
Description
## Description
`GestureManager.SetupGestureManager()` guards against a null handler, but not against a handler whose `PlatformView` is null. This produces an `InvalidOperationException` inside `GesturePlatformManager..ctor` that escapes the managed→ObjC boundary and causes a **SIGABRT** on iOS.
## Steps to Reproduce
The crash occurs when a `Border` control is re-attached to a window while its handler has already been disconnected (`DisconnectHandler` called, `PlatformView = null`). This happens in two common scenarios:
- **Navigation cache re-entry**: MAUI Shell caches a page; on re-navigation the `Window` property changes before handlers are fully reconnected.
- **CollectionView cell recycling**: A `Border` inside a `DataTemplate` is reused after its handler was disconnected during virtualisation.
Exact sequence:
1. `Border` handler connects → `GestureManager` subscribes to `VirtualView.WindowChanged`.
2. Handler is disconnected → `PlatformView = null`. Depending on timing, `HandlerChanging` may not fire (navigation cache path), so the gesture manager subscription survives.
3. `VirtualView.Window` changes → `GestureManager.OnWindowChanged` → `SetupGestureManager()`.
4. Guard `handler == null` passes (handler reference is non-null).
5. `_platformView != handler.PlatformView` (null) → `DisconnectGestures()` → `GesturePlatformManager = null`.
6. `new GesturePlatformManager(handler)` is called.
7. Inside constructor: `_handler?.ToPlatform()` → `handler.PlatformView` is null → `InvalidOperationException: Unable to convert Microsoft.Maui.Controls.Border to UIKit.UIView`.
8. Exception escapes `MovedToWindow()` through the managed→ObjC boundary → **SIGABRT**.
## The Bug in the Source
**`GestureManager.cs` — `SetupGestureManager()` (current `main`):**
```csharp
var handler = _view.Handler;
if (handler == null ||
(_didHaveWindow && _view.Window == null))
{
DisconnectGestures();
return;
}
// ...reaches here with handler.PlatformView == null
GesturePlatformManager = new GesturePlatformManager(handler); // ← crashes
```
**`GesturePlatformManager.iOS.cs` — constructor:**
```csharp
if (_handler?.ToPlatform() is not PlatformView target)
throw new ArgumentNullException(nameof(handler.PlatformView));
// ^ This is reached when PlatformView is null, but ToPlatform() actually
// throws InvalidOperationException before the null check here fires.
```
## Expected Behaviour
`SetupGestureManager()` should return early (or call `DisconnectGestures()`) when `handler.PlatformView` is null, rather than proceeding to construct a `GesturePlatformManager` that will immediately crash.
## Proposed Fix
Add a single null guard in `SetupGestureManager()`:
```csharp
if (handler == null ||
handler.PlatformView == null || // ← add this line
(_didHaveWindow && _view.Window == null))
{
DisconnectGestures();
return;
}
```
## Confirmed Stack Trace (from iOS breadcrumb log)
```
[Managed->ObjC]: InvalidOperationException: PlatformView cannot be null here
at ImageHandler.OnWindowChanged() → MauiImageView.MovedToWindow()
[Managed->ObjC]: InvalidOperationException: Unable to convert Border to UIKit.UIView
at ElementExtensions.ToPlatform(IElement view)
at ElementHandlerExtensions.ToPlatform(IElementHandler elementHandler)
at GesturePlatformManager..ctor(IViewHandler handler)
at GestureManager.SetupGestureManager()
at GestureManager.OnWindowChanged(Object sender, EventArgs e)
at VisualElement.OnWindowChanged(BindableObject bindable, Object oldValue, Object newValue)
at BindableObject.OnBindablePropertySet(...)
Unhandled(AppDomain): InvalidOperationException: Unable to convert Border to UIKit.UIView
UnhandledException: same → SIGABRT
```
Confirmed on a production pilot fleet (iPad12,1, iOS 26.2.1, build targeting net10.0-ios, MAUI 10.0.60). The crash reproduces across multiple stores and devices and is the top-crashing issue in our fleet.
## Version
- .NET: 10.0 (net10.0-ios)
- MAUI: 10.0.60
- iOS: 26.2.1 (also reproduced on 26.4.x and 26.5 beta)
- Device: iPad12,1 (iPad Pro 12.9")
## Workaround
Override `CreatePlatformView()` in a custom `BorderHandler` to return a subclass that catches the exception in `MovedToWindow()` before it escapes to UIKit:
```csharp
public class CustomBorderHandler : Microsoft.Maui.Handlers.BorderHandler
{
protected override ContentView CreatePlatformView() => new SafeContentView();
sealed class SafeContentView : ContentView
{
public override void MovedToWindow()
{
try { base.MovedToWindow(); }
catch (InvalidOperationException ex)
when (ex.Message.Contains("Unable to convert") ||
ex.Message.Contains("PlatformView cannot be null"))
{ }
}
}
}
```
This absorbs the exception before it crosses the managed→ObjC boundary. MAUI re-establishes gesture recognizers on the next proper `ConnectHandler` cycle.
Note: `CustomContentViewHandler` likely has the same vulnerability (same `ContentView` platform type, same `GestureManager` subscription path) but we have not confirmed crashes from it.
Contributor guide
Research direction
Start in GestureManager.cs at SetupGestureManager() and compare its handler checks with the constructor in GesturePlatformManager.iOS.cs. Add coverage for a disconnected handler whose PlatformView is null, then verify the window-change path disconnects safely without reaching the constructor or causing SIGABRT.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, ios
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100