Skip to content

[iOS] Fix Shell page memory leak when using TitleView with x:Name#35082

Merged
kubaflo merged 6 commits intodotnet:inflight/currentfrom
Shalini-Ashokan:fix-34975
Apr 23, 2026
Merged

[iOS] Fix Shell page memory leak when using TitleView with x:Name#35082
kubaflo merged 6 commits intodotnet:inflight/currentfrom
Shalini-Ashokan:fix-34975

Conversation

@Shalini-Ashokan
Copy link
Copy Markdown
Contributor

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!

Issue Details

When Shell.TitleView and x:Name are used together on a ContentPage, the page is never garbage collected after back-navigation on iOS. This causes a memory leak that grows with every navigation round-trip.

Root Cause

x:Name on a ContentPage creates a NameScope that holds a reference back to the page, and this gets attached to the TitleView children. On back-navigation, the page is disposed but the native iOS UINavigationItem.TitleView reference was never cleared. Since iOS UIKit still holds the TitleView, the whole chain — TitleView → NameScope → Page — stays alive and the page is never collected.

Description of Change

Setting NavigationItem.TitleView = null during page disposal releases UIKit's strong native reference to the TitleViewContainer. This breaks the reference chain and allows the page to be garbage collected normally.

Validated the behavior in the following platforms

  • Android
  • Windows
  • iOS
  • Mac

Issues Fixed

Fixes #34975

Output ScreenShot

Before After
34975-iOS-BeforeFix.mov
34975-iOS-Fix.mov

Shalini-Ashokan and others added 6 commits April 16, 2026 19:30
When Shell.TitleView is used together with x:Name, the page was not
garbage collected on Windows. The MauiToolbar's ContentPresenter held
a reference to the native TitleView, which through the MAUI handler
chain referenced the TitleView VisualElement, whose NameScope entries
pointed back to the page.

Fix: in OnHandlerChanging (when the handler is set to null on navigation),
disconnect the TitleView's handler and null out the native MauiToolbar
TitleView reference to break the reference chain.

Also updates the [Issue] attribute and test description to reflect that
all three platforms (iOS, Android, Windows) are affected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35082

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35082"

@dotnet-policy-service dotnet-policy-service Bot added the community ✨ Community Contribution label Apr 22, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Hey there @@Shalini-Ashokan! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Apr 22, 2026
@Tamilarasan-Paranthaman Tamilarasan-Paranthaman added platform/ios area-controls-shell Shell Navigation, Routes, Tabs, Flyout labels Apr 22, 2026
@sheiksyedm sheiksyedm marked this pull request as ready for review April 22, 2026 10:57
Copilot AI review requested due to automatic review settings April 22, 2026 10:57
@sheiksyedm sheiksyedm added this to the .NET 10 SR7 milestone Apr 22, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a 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 addresses an iOS-specific Shell navigation memory leak where a Shell.TitleView combined with a page-level x:Name prevents the page from being garbage collected after navigating back.

Changes:

  • Clear UINavigationItem.TitleView during ShellPageRendererTracker disposal on iOS to break the native retain chain.
  • Add a HostApp repro (Issue34975 + Issue34975SecondPage) that creates a weak-reference target and validates GC after a navigation round-trip.
  • Add an Appium UI test (Issue34975) that drives the repro and asserts the leak is gone.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellPageRendererTracker.cs Clears the native NavigationItem.TitleView on disposal to allow managed page GC.
src/Controls/tests/TestCases.HostApp/Issues/Issue34975.cs Adds a Shell-based repro harness that navigates to a TitleView + x:Name page and checks GC.
src/Controls/tests/TestCases.HostApp/Issues/Issue34975SecondPage.xaml Repro page using Shell.TitleView and x:Name (the leak trigger).
src/Controls/tests/TestCases.HostApp/Issues/Issue34975SecondPage.xaml.cs Tracks created page instances via WeakReference for GC verification.
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34975.cs Adds an Appium test that runs the repro flow and asserts “Test passed”.

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Apr 22, 2026

🤖 AI Summary

👋 @Shalini-Ashokan — new AI review results are available. Please review the latest session below.

📊 Review Session94fe62a · modified the test case · 2026-04-23 10:19 UTC
🚦 Gate — Test Before & After Fix

Test Verification Report

Date: 2026-04-22 20:22:15 | Platform: ANDROID | Status: ❌ FAILED

Summary

Check Expected Actual Result
Tests WITHOUT fix FAIL PASS
Tests WITH fix PASS PASS

❌ Final Verdict

VERIFICATION FAILED

Tests PASSED without fix (should have failed)

  • The tests don't actually detect the bug
  • Tests may not be testing the right behavior

Possible causes:

  1. Wrong fix files specified
  2. Tests don't actually test the fixed behavior
  3. The issue was already fixed in base branch
  4. Build caching - try clean rebuild
  5. Test needs different setup or conditions

Configuration

Platform: android
Test Filter: Issue34975
Base Branch: main
Merge Base: d5e39b0

Fix Files

  • src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellPageRendererTracker.cs

Test Results Details

Test Run 1: WITHOUT Fix

Expected: Tests should FAIL (bug is present)
Actual: Tests PASSED ❌

Test Summary:

  • Total:
  • Passed: True
  • Failed:
  • Skipped:
View full test output (without fix)
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 670 ms).
  Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 3.13 sec).
  Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 5.76 sec).
  Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 1.92 sec).
  Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 731 ms).
  Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 29 ms).
  Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 36 ms).
  Restored /home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 67 ms).
  Restored /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 677 ms).
  Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 1.68 sec).
  1 of 11 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:07:30.26
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0


Test Run 2: WITH Fix

Expected: Tests should PASS (bug is fixed)
Actual: Tests PASSED ✅

Test Summary:

  • Total:
  • Passed: True
  • Failed:
  • Skipped:
View full test output (with fix)
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: Mono.AndroidTools.InstallFailedException: Unexpected install output: cmd: Failure calling service package: Broken pipe (32) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:  [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]

Build FAILED.

/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: Mono.AndroidTools.InstallFailedException: Unexpected install output: cmd: Failure calling service package: Broken pipe (32) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:  [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
    0 Warning(s)
    1 Error(s)

Time Elapsed 00:11:19.13
* daemon not running; starting now at tcp:5037
* daemon started successfully
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
  Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13910943
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:09:07.71
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0


Logs

  • Full verification log: /home/vsts/work/1/s/CustomAgentLogsTmp/PRState/35082/PRAgent/gate/verify-tests-fail/verification-log.txt
  • Test output without fix: /home/vsts/work/1/s/CustomAgentLogsTmp/PRState/35082/PRAgent/gate/verify-tests-fail/test-without-fix.log
  • Test output with fix: /home/vsts/work/1/s/CustomAgentLogsTmp/PRState/35082/PRAgent/gate/verify-tests-fail/test-with-fix.log
  • UI test logs: CustomAgentLogsTmp/UITests/

🔍 Pre-Flight — Context & Validation

Issue: #34975 - [iOS] Title view memory leak
PR: #35082 - [iOS] Fix Shell page memory leak when using TitleView with x:Name
Platforms Affected: iOS (fix), all platforms (tests run on all)
Files Changed: 1 implementation (ShellPageRendererTracker.cs), 4 test files

Key Findings

  • Fix is iOS-only: ShellPageRendererTracker.cs is the iOS Shell compatibility handler
  • The Gate was run on Android — the fix doesn't affect Android, so the test passes on Android regardless of whether the fix is applied (explains Gate ❌ FAILED: "tests PASSED without fix" on Android)
  • Test Issue34975 is tagged PlatformAffected.iOS in HostApp but the Gate ran with Android platform
  • The fix sets NavigationItem.TitleView = null before tvc.Disconnect() in Dispose(), breaking the UIKit → NameScope → Page retain chain
  • tvc.Disconnect() is a no-op (empty method body) — the fix is entirely in the new null assignment
  • Task.Delay(500) in the test creates a timing race for disposal completion

Code Review Summary

Verdict: LGTM
Confidence: medium
Errors: 0 | Warnings: 2 | Suggestions: 1

Key code review findings:

  • ⚠️ ShellPageRendererTracker.cs:1315tvc.Disconnect() is a no-op (empty body); comment implies it contributes to the fix but it doesn't. A future maintainer might remove the NavigationItem.TitleView = null line thinking Disconnect() handles cleanup.
  • ⚠️ Issue34975.cs:~41Task.Delay(500) is fragile; if Shell disposal hasn't completed in 500ms, WaitForGC may run too early. A deterministic trigger (e.g., OnNavigatedFrom) would be more robust.
  • 💡 Test tracks WeakReference(this) for the page but not for the TitleViewContainer — a future regression via the TitleViewContainer path could go undetected.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #35082 Set NavigationItem.TitleView = null before tvc.Disconnect() in Dispose() ❌ FAILED (Gate — Android platform, iOS-only fix) ShellPageRendererTracker.cs Original PR

Gate Failure Analysis

The Gate ❌ FAILED with "tests PASSED without fix" on Android. This is a test scope mismatch, not a test quality failure:

  • The fix is in ShellPageRendererTracker.cs which is an iOS-only file (compiled only for net*-ios and net*-maccatalyst)
  • The Gate ran the test on Android where the fix file doesn't even compile
  • The memory leak is iOS-specific (UIKit NavigationItem.TitleView strong reference)
  • On Android, the GC test passes unconditionally because Android has no such leak
  • The Gate would need to run on iOS to properly detect the bug

🔬 Code Review — Deep Analysis

Code Review — PR #35082

Independent Assessment

What this changes: In ShellPageRendererTracker.Dispose(bool disposing), before calling tvc.Disconnect() on the TitleViewContainer, the code now explicitly sets NavigationItem.TitleView = null first. This clears UIKit's strong native reference to the TitleViewContainer before the managed-side cleanup runs. Four test files are added: a HostApp page (Issue34975.cs), a second-page XAML pair (Issue34975SecondPage.xaml[.cs]), and an NUnit UI test.

Inferred motivation: A memory leak on iOS when navigating back from a Shell page that uses a TitleView together with x:Name on the root element. The page wasn't being GC'd after back-navigation.


Reconciliation with PR Narrative

Author claims: When Shell.TitleView and x:Name are used together on a ContentPage, UIKit's NavigationItem.TitleView retains the TitleViewContainer, which in turn holds the MAUI view tree, NameScope, and a strong reference back to the page — preventing GC. Setting NavigationItem.TitleView = null during disposal breaks the chain.

Agreement/disagreement: The root cause analysis and fix are accurate. The key observation: NavigationItem = null is set at line 1327 (outside the if (disposing) guard), but that only clears the C# reference to the navigation item. UIKit's native reference to the TitleView native object persists until it's explicitly cleared. The fix inserts that explicit clear at the right moment.


Findings

⚠️ Warning — UIContainerView.Disconnect() is a no-op; comment overstates its role

UIContainerView.Disconnect() (Shell/iOS/UIContainerView.cs) has an empty body:

internal void Disconnect()
{
}

The comment on the new lines says "…so clearing this native reference is necessary to allow GC," which is accurate — but it implies tvc.Disconnect() on the following line also contributes to the fix. It doesn't. tvc.Disconnect() is a no-op and was already present before this PR. The entire fix is accomplished solely by NavigationItem.TitleView = null. A future maintainer reading the code will wonder what Disconnect() is doing for memory there, or may remove the NavigationItem.TitleView = null line thinking Disconnect() handles it. Worth updating the comment to say "this line alone is what breaks the chain; Disconnect() currently has no body."

⚠️ Warning — Task.Delay(500) in HostApp navigation handler

Issue34975.cs (HostApp), line ~41:

// A small delay lets that continuation run before we expose CheckMemoryButton.
await Task.Delay(500);

Per MAUI review rules: Task.Delay in tests needs justification — "How do we know 500ms is enough?" On slow CI or a heavily loaded simulator this delay could expire before the Shell back-navigation has fully completed its disposal chain, causing GarbageCollectionHelper.WaitForGC to be invoked while the NavigationItem.TitleView = null assignment hasn't been reached yet. The WaitForGC 5-second timeout provides a safety net, but the race is real. A deterministic signal (e.g., overriding OnNavigatedFrom or hooking Disappearing on the second page to reveal the button) would be more robust than a fixed delay.

💡 Suggestion — Test only tracks the page, not the TitleViewContainer

Issue34975SecondPage.xaml.cs adds a WeakReference(this) for the page. If a future regression re-introduces the leak via the TitleViewContainer (instead of the page), the test would pass even though memory is still leaking. Tracking a WeakReference to the TitleViewContainer would make the test cover the exact UIKit object the fix operates on.


Devil's Advocate

On the ⚠️ about Disconnect(): One could argue the code was always calling Disconnect() after clearing TitleView in UpdateTitleView(), so keeping the same call in Dispose preserves symmetry even if it's a no-op today. That's fair — the call is harmless. But it still risks misleading future readers.

On approving the fix overall: Could NavigationItem.TitleView = null during disposal cause a side effect? The only listener on TitleView changes is the UIKit layout system, which during disposal is irrelevant. UpdateTitleView() already performs this exact null assignment at line 323 for the non-null → null transition; Dispose is a subset of that path. No re-entrancy risk.

On test platform scope: The fix is iOS-only (ShellPageRendererTracker.cs is iOS-only). The test runs on all platforms. On Android/Windows there is no leak, so GarbageCollectionHelper.WaitForGC will succeed normally. This is correct behavior per MAUI test guidelines (tests should run on all platforms unless technically blocked).


Verdict: LGTM

Confidence: medium
Summary: The fix is correct, surgical, and well-motivated. Setting NavigationItem.TitleView = null before managed cleanup is exactly the right place to break the UIKit → NameScope → Page retain chain, and it mirrors the pattern already used in UpdateTitleView(). The two warnings above are comment-level and test-robustness concerns, not correctness blockers.


🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #35082 Set NavigationItem.TitleView = null before tvc.Disconnect() in Dispose() ❌ FAILED (Gate — Android, iOS-only fix) ShellPageRendererTracker.cs Original PR
1 try-fix (claude-opus-4.6) Implement UIContainerView.Disconnect() to sever its own managed refs (remove platform subview, null _renderer/_platformView) ✅ PASS (Android caveat*) UIContainerView.cs Fixes code review concern about Disconnect() no-op
2 try-fix (claude-sonnet-4.6) Proactive cleanup in SetDisappeared() at navigation time rather than disposal ✅ PASS (Android caveat*) ShellPageRendererTracker.cs Different timing hypothesis — earlier cleanup
3 try-fix (gpt-5.3-codex) Clear NameScope from TitleView tree during disposal ✅ PASS (Android caveat*) ShellPageRendererTracker.cs Targets managed NameScope chain
4 try-fix (gpt-5.4) RemoveLogicalChild for TitleView in Dispose() ✅ PASS (Android caveat*) ShellPageRendererTracker.cs Detaches from logical tree

All Android passes are non-validating because the fix files are iOS-only (compiled only for net-ios/maccatalyst). Android has no UIKit reference chain and cannot exhibit this leak. Tests pass on Android regardless of the fix. True validation requires iOS.

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 2 No "NO NEW IDEAS — the five approaches cover the practical fix space for this bug"

Exhausted: Yes
Selected Fix: PR's fix — Most surgical (single line), directly addresses UIKit strong reference, mirrors existing UpdateTitleView() pattern, no side effects, code review LGTM.

Platform Caveat Note

The Gate ❌ FAILED because it ran on Android for an iOS-only fix. The fix is in ShellPageRendererTracker.cs which only compiles for iOS/MacCatalyst. On Android, the memory leak doesn't exist, so the test passes unconditionally with or without any code changes to the iOS file. The Gate result reflects a platform scope mismatch, not a test or fix quality issue. The Gate should be re-run on iOS to properly validate the fix.


📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE iOS-only fix, Gate platform mismatch identified
Code Review LGTM (medium) 0 errors, 2 warnings, 1 suggestion
Gate ❌ FAILED Android platform — tests passed WITHOUT fix (iOS-only code not compiled for Android)
Try-Fix ✅ COMPLETE 4 attempts, all PASS (Android caveat — same platform mismatch as Gate)
Report ✅ COMPLETE

Code Review Impact on Try-Fix

Code review identified that tvc.Disconnect() is a no-op, which prompted Attempt 1 (claude-opus-4.6) to specifically address this by implementing UIContainerView.Disconnect() as a substantive cleanup. The code review warning about Task.Delay(500) being fragile was noted in all models' analyses but not addressed in any try-fix attempt (out of scope for try-fix). The LGTM verdict with medium confidence meant all models approached the PR's fix as correct-but-improvable, leading to exploration of alternative fix locations (timing, NameScope, logical tree).

Summary

PR #35082 fixes an iOS-only Shell page memory leak where using Shell.TitleView together with x:Name on a ContentPage prevents GC after back-navigation. The fix is a single-line addition: NavigationItem.TitleView = null before tvc.Disconnect() in ShellPageRendererTracker.Dispose(). The fix is correct, surgical, and code review returned LGTM.

However, the Gate ❌ FAILED because it was run on Android with an iOS-only fix file. ShellPageRendererTracker.cs is only compiled for iOS/MacCatalyst targets — it does not exist in the Android build. The test passed on Android without the fix because Android has no UIKit reference chain and cannot exhibit this leak. This is a platform scope mismatch in the Gate configuration, not a defect in the test or the fix.

REQUEST CHANGES is recommended due to the Gate failure (per review policy), combined with two test quality warnings from code review. These are actionable improvements the author should address before the Gate can be re-run on the correct platform.

Root Cause

UIKit's NavigationItem.TitleView holds a strong native reference to TitleViewContainer. When the page uses x:Name, MAUI's NameScope registers the page with the view tree, creating: UIKit → TitleViewContainer → MAUI view tree → NameScope → Page. When navigating back, the page is disposed but UIKit still holds the native TitleView, keeping the entire chain alive. Setting NavigationItem.TitleView = null explicitly during disposal breaks the head of this chain.

Fix Quality

The PR's fix is correct and minimal. Code review verdict: LGTM (medium confidence), 0 errors, 2 warnings:

  1. tvc.Disconnect() is a no-op — The comment in the new code implies Disconnect() contributes to the fix, but its body is empty. Only NavigationItem.TitleView = null actually breaks the chain. Risk: future maintainer may remove the null assignment thinking Disconnect() handles it. The comment should be clarified.

  2. Task.Delay(500) in test is fragile — If Shell's disposal chain takes longer than 500ms on a slow CI machine, WaitForGC may be invoked before NavigationItem.TitleView = null has been called. The 5-second WaitForGC timeout mitigates this, but a deterministic trigger (e.g., OnNavigatedFrom) would be more robust.

Gate re-run required on iOS to properly validate this fix. All try-fix passes were on Android, which cannot exercise iOS-only code.


🧪 UI Tests — Category Detection & Results

Build #1391786 | 🔄 inProgress | / passed (0%)

🎯 Detected categories: Shell — ran 7 of 143 matrix cells (skipped 136)


@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Apr 23, 2026
@kubaflo kubaflo changed the base branch from main to inflight/current April 23, 2026 11:15
@kubaflo kubaflo merged commit 71a599f into dotnet:inflight/current Apr 23, 2026
35 checks passed
PureWeen pushed a commit that referenced this pull request Apr 28, 2026
…5082)

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
When Shell.TitleView and x:Name are used together on a ContentPage, the
page is never garbage collected after back-navigation on iOS. This
causes a memory leak that grows with every navigation round-trip.

### Root Cause
x:Name on a ContentPage creates a NameScope that holds a reference back
to the page, and this gets attached to the TitleView children. On
back-navigation, the page is disposed but the native iOS
UINavigationItem.TitleView reference was never cleared. Since iOS UIKit
still holds the TitleView, the whole chain — TitleView → NameScope →
Page — stays alive and the page is never collected.

### Description of Change
Setting NavigationItem.TitleView = null during page disposal releases
UIKit's strong native reference to the TitleViewContainer. This breaks
the reference chain and allows the page to be garbage collected
normally.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #34975 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/9c914487-6ce6-4fd8-b9dc-81c98f56e810"
>| <video
src="https://github.com/user-attachments/assets/f0266d6a-9561-4275-9015-c05d9570be78">|

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Apr 29, 2026
…5082)

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
When Shell.TitleView and x:Name are used together on a ContentPage, the
page is never garbage collected after back-navigation on iOS. This
causes a memory leak that grows with every navigation round-trip.

### Root Cause
x:Name on a ContentPage creates a NameScope that holds a reference back
to the page, and this gets attached to the TitleView children. On
back-navigation, the page is disposed but the native iOS
UINavigationItem.TitleView reference was never cleared. Since iOS UIKit
still holds the TitleView, the whole chain — TitleView → NameScope →
Page — stays alive and the page is never collected.

### Description of Change
Setting NavigationItem.TitleView = null during page disposal releases
UIKit's strong native reference to the TitleViewContainer. This breaks
the reference chain and allows the page to be garbage collected
normally.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #34975 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/9c914487-6ce6-4fd8-b9dc-81c98f56e810"
>| <video
src="https://github.com/user-attachments/assets/f0266d6a-9561-4275-9015-c05d9570be78">|

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
github-actions Bot pushed a commit that referenced this pull request May 6, 2026
…5082)

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
When Shell.TitleView and x:Name are used together on a ContentPage, the
page is never garbage collected after back-navigation on iOS. This
causes a memory leak that grows with every navigation round-trip.

### Root Cause
x:Name on a ContentPage creates a NameScope that holds a reference back
to the page, and this gets attached to the TitleView children. On
back-navigation, the page is disposed but the native iOS
UINavigationItem.TitleView reference was never cleared. Since iOS UIKit
still holds the TitleView, the whole chain — TitleView → NameScope →
Page — stays alive and the page is never collected.

### Description of Change
Setting NavigationItem.TitleView = null during page disposal releases
UIKit's strong native reference to the TitleViewContainer. This breaks
the reference chain and allows the page to be garbage collected
normally.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #34975 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/9c914487-6ce6-4fd8-b9dc-81c98f56e810"
>| <video
src="https://github.com/user-attachments/assets/f0266d6a-9561-4275-9015-c05d9570be78">|

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-shell Shell Navigation, Routes, Tabs, Flyout community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/ios s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS] Title view memory leak

7 participants