Description
On iOS and Mac Catalyst, MAUI Map pages that contain MapElements can remain retained after they are popped from navigation. The repro pushes and pops 30 real Shell pages containing Map controls with Circle elements, then forces full GC and checks weak references.
The control scenario, using maps without MapElements, collects normally. The MapElements scenario retains all 30 MAUI Map views, all 30 payload view models, and all 1,200 map elements, retaining about 60 MB of managed payload after GC.
Clearing Map.MapElements in OnDisappearing() avoids the retention, which suggests stale map-element tracking/native overlay state is held through the iOS/Mac Catalyst pooled native map path rather than normal app ownership. Developers should not need to manually clear Map.MapElements for popped pages to become collectible.
Steps to Reproduce
- Check out the repro branch:
git clone https://github.com/AdamEssenmacher/maui.git
cd maui
git checkout repro/map-pool-map-elements-leak
- Build the in-tree MAUI build tasks:
dotnet build Microsoft.Maui.BuildTasks.slnf
- Run the Mac Catalyst repro app:
dotnet run --project src/Controls/samples/MapPoolLeakRepro/MapPoolLeakRepro.csproj -f net10.0-maccatalyst -p:RuntimeIdentifier=maccatalyst-arm64
- Leave the default settings:
Pages/run: 30
MapElements/page: 40
Payload MB/page: 2
Dwell ms/page: 120
-
Click Run control and wait for completion.
-
Click Run leaky MapElements and wait for completion.
-
Optional: click Run mitigation to show that clearing Map.MapElements before disconnect avoids the retention.
Actual Result
The control scenario collects normally:
MAUI Map views: 0/30
payload view models: 0/30
map elements: 0/0
Payload retained by alive view models: 0 B
The MapElements scenario retains the old page graph after full GC:
MAUI Map views: 30/30
payload view models: 30/30
map elements: 1200/1200
Payload retained by alive view models: 60.0 MB
Managed heap delta after GC: 63.7 MB
Expected Result
After the pages are popped and full GC runs, the old MAUI Map views, their payload view models, and MapElements should be collectible, matching the control scenario.
Notes
The mitigation scenario clears Map.MapElements in OnDisappearing() and returns to 0/30 retained maps/view models and 0/1200 retained elements. This demonstrates an app-level workaround and points to stale MapElement state retained through the iOS/Mac Catalyst pooled native map path.
Link to public reproduction project repository
https://github.com/AdamEssenmacher/maui/tree/repro/map-pool-map-elements-leak/src/Controls/samples/MapPoolLeakRepro
Version with bug
10.0.60
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS, macOS
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output
Description
On iOS and Mac Catalyst, MAUI Map pages that contain MapElements can remain retained after they are popped from navigation. The repro pushes and pops 30 real Shell pages containing Map controls with Circle elements, then forces full GC and checks weak references.
The control scenario, using maps without MapElements, collects normally. The MapElements scenario retains all 30 MAUI Map views, all 30 payload view models, and all 1,200 map elements, retaining about 60 MB of managed payload after GC.
Clearing Map.MapElements in OnDisappearing() avoids the retention, which suggests stale map-element tracking/native overlay state is held through the iOS/Mac Catalyst pooled native map path rather than normal app ownership. Developers should not need to manually clear Map.MapElements for popped pages to become collectible.
Steps to Reproduce
git clone https://github.com/AdamEssenmacher/maui.git cd maui git checkout repro/map-pool-map-elements-leakClick
Run controland wait for completion.Click
Run leaky MapElementsand wait for completion.Optional: click
Run mitigationto show that clearingMap.MapElementsbefore disconnect avoids the retention.Actual Result
The control scenario collects normally:
The MapElements scenario retains the old page graph after full GC:
Expected Result
After the pages are popped and full GC runs, the old MAUI
Mapviews, their payload view models, andMapElements should be collectible, matching the control scenario.Notes
The mitigation scenario clears
Map.MapElementsinOnDisappearing()and returns to0/30retained maps/view models and0/1200retained elements. This demonstrates an app-level workaround and points to staleMapElementstate retained through the iOS/Mac Catalyst pooled native map path.Link to public reproduction project repository
https://github.com/AdamEssenmacher/maui/tree/repro/map-pool-map-elements-leak/src/Controls/samples/MapPoolLeakRepro
Version with bug
10.0.60
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS, macOS
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output