Skip to content

Commit c49b7e8

Browse files
committed
Disconnect Shell flyout container handlers
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 926cb3a5-a29d-45bd-8c25-35da659b8dfd
1 parent 54a1b7f commit c49b7e8

3 files changed

Lines changed: 53 additions & 15 deletions

File tree

src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutContentRenderer.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,17 +151,13 @@ void UpdateFlyoutFooter(View view)
151151
int previousIndex = GetPreviousIndex(_footerView);
152152
if (_footer is not null)
153153
{
154-
var oldRenderer = (IPlatformViewHandler)_footer.Handler;
155154
var oldFooterView = _footerView;
156155
_footer.MeasureInvalidated -= OnFooterMeasureInvalidated;
157156
_tableViewController.FooterView = null;
158-
_footerView?.Disconnect();
159157
_footerView = null;
160158
_uIViews[FooterIndex] = null;
161159
oldFooterView?.RemoveFromSuperview();
162-
163-
_footer.Handler = null;
164-
oldRenderer?.DisconnectHandler();
160+
oldFooterView?.Dispose();
165161
}
166162

167163
_footer = view;

src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerView.cs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ namespace Microsoft.Maui.Controls.Platform.Compatibility
1111
public class UIContainerView : UIView
1212
{
1313
readonly View _view;
14-
[UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Renderer is owned by the container view and cleared in Dispose(bool).")]
14+
[UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Renderer is owned by the container view and disconnected and cleared in Disconnect.")]
1515
IPlatformViewHandler _renderer;
16-
[UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Platform view is owned as a UIKit subview and cleared in Dispose(bool).")]
16+
[UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Platform view is owned as a UIKit subview and detached and cleared in Disconnect.")]
1717
UIView _platformView;
1818
bool _disposed;
1919
double _measuredHeight;
2020

21-
[UnconditionalSuppressMessage("Memory", "MEM0001", Justification = "Event is unsubscribed by ShellFlyoutLayoutManager.TearDown when the header view is released.")]
21+
[UnconditionalSuppressMessage("Memory", "MEM0001", Justification = "Event subscribers are cleared in Dispose(bool).")]
2222
internal event EventHandler HeaderSizeChanged;
2323

2424
public UIContainerView(View view)
@@ -105,7 +105,6 @@ public override CGSize SizeThatFits(CGSize size)
105105

106106
public override void WillRemoveSubview(UIView uiview)
107107
{
108-
Disconnect();
109108
base.WillRemoveSubview(uiview);
110109
}
111110

@@ -141,6 +140,16 @@ public override void LayoutSubviews()
141140

142141
internal void Disconnect()
143142
{
143+
var renderer = _renderer;
144+
var platformView = _platformView;
145+
146+
_renderer = null;
147+
_platformView = null;
148+
149+
if (platformView?.Superview == this)
150+
platformView.RemoveFromSuperview();
151+
152+
renderer?.DisconnectHandler();
144153
}
145154

146155
protected override void Dispose(bool disposing)
@@ -151,12 +160,7 @@ protected override void Dispose(bool disposing)
151160
if (disposing)
152161
{
153162
Disconnect();
154-
155-
if (_platformView.Superview == this)
156-
_platformView.RemoveFromSuperview();
157-
158-
_renderer = null;
159-
_platformView = null;
163+
HeaderSizeChanged = null;
160164
_disposed = true;
161165
}
162166

src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.iOS.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,44 @@ await CreateHandlerAndAddToWindow<ShellHandler>(shell, handler =>
649649
});
650650
}
651651

652+
[Fact(DisplayName = "Disposed Shell Flyout Content Disconnects Header And Footer Handlers")]
653+
public async Task DisposedShellFlyoutContentDisconnectsHeaderAndFooterHandlers()
654+
{
655+
SetupBuilder();
656+
var header = new Label { Text = "Header" };
657+
var footer = new Label { Text = "Footer" };
658+
var shell = await CreateShellAsync(shell =>
659+
{
660+
shell.Items.Add(new ContentPage());
661+
shell.FlyoutHeader = header;
662+
shell.FlyoutFooter = footer;
663+
});
664+
665+
await CreateHandlerAndAddToWindow<ShellHandler>(shell, handler =>
666+
{
667+
var flyoutContent = handler.ViewController
668+
.ChildViewControllers
669+
.OfType<ShellFlyoutContentRenderer>()
670+
.First();
671+
var headerPlatformView = header.ToPlatform();
672+
var footerPlatformView = footer.ToPlatform();
673+
674+
Assert.NotNull(header.Handler);
675+
Assert.NotNull(footer.Handler);
676+
Assert.NotNull(headerPlatformView.Superview);
677+
Assert.NotNull(footerPlatformView.Superview);
678+
679+
flyoutContent.Dispose();
680+
681+
Assert.Null(header.Handler);
682+
Assert.Null(footer.Handler);
683+
Assert.Null(headerPlatformView.Superview);
684+
Assert.Null(footerPlatformView.Superview);
685+
686+
return Task.CompletedTask;
687+
});
688+
}
689+
652690
[Fact(DisplayName = "Shell Flyout Renderer Disposal Is Idempotent After Native Teardown")]
653691
public Task ShellFlyoutRendererDisposalIsIdempotentAfterNativeTeardown() =>
654692
InvokeOnMainThreadAsync(() =>

0 commit comments

Comments
 (0)