Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Microsoft.Maui-dev.sln
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtils", "src\TestUtils\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.UnitTests", "src\Core\tests\UnitTests\Core.UnitTests.csproj", "{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.ExternalBackend", "src\Core\tests\ExternalBackend\Core.ExternalBackend.csproj", "{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E8AD265B-3C67-4640-AC58-A522F9FB3361}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C564DDD6-DE79-45CD-88EA-3F690481572A}"
Expand Down Expand Up @@ -309,6 +311,10 @@ Global
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Release|Any CPU.Build.0 = Release|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Release|Any CPU.Build.0 = Release|Any CPU
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -679,6 +685,7 @@ Global
{DAAC2822-63B6-4DE0-83AE-04873CD2F364} = {72397ADB-40A8-4B8E-8E08-2DBE2803C845}
{FBB3270F-1924-4A72-845E-A6DF39C402F6} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3} = {C564DDD6-DE79-45CD-88EA-3F690481572A}
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41} = {C564DDD6-DE79-45CD-88EA-3F690481572A}
{E8AD265B-3C67-4640-AC58-A522F9FB3361} = {09C264E9-E3F3-4586-9151-DCBB1F6DA7AB}
{C564DDD6-DE79-45CD-88EA-3F690481572A} = {09C264E9-E3F3-4586-9151-DCBB1F6DA7AB}
{50C758FE-4E10-409A-94F5-A75480960864} = {459BF674-83CB-46F6-881F-A2D2117DBF4D}
Expand Down
1 change: 1 addition & 0 deletions Microsoft.Maui-mac.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"src\\Core\\tests\\Benchmarks\\Core.Benchmarks.csproj",
"src\\Core\\tests\\DeviceTests.Shared\\Core.DeviceTests.Shared.csproj",
"src\\Core\\tests\\DeviceTests\\Core.DeviceTests.csproj",
"src\\Core\\tests\\ExternalBackend\\Core.ExternalBackend.csproj",
"src\\Core\\tests\\UnitTests\\Core.UnitTests.csproj",
"src\\Essentials\\samples\\Sample.Server.WebAuthenticator\\Essentials.Sample.Server.WebAuthenticator.csproj",
"src\\Essentials\\samples\\Samples.Server.Passkeys\\Essentials.Samples.Server.Passkeys.csproj",
Expand Down
7 changes: 7 additions & 0 deletions Microsoft.Maui-vscode.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtils", "src\TestUtils\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.UnitTests", "src\Core\tests\UnitTests\Core.UnitTests.csproj", "{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.ExternalBackend", "src\Core\tests\ExternalBackend\Core.ExternalBackend.csproj", "{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E8AD265B-3C67-4640-AC58-A522F9FB3361}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C564DDD6-DE79-45CD-88EA-3F690481572A}"
Expand Down Expand Up @@ -272,6 +274,10 @@ Global
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Release|Any CPU.Build.0 = Release|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Release|Any CPU.Build.0 = Release|Any CPU
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -591,6 +597,7 @@ Global
{DAAC2822-63B6-4DE0-83AE-04873CD2F364} = {72397ADB-40A8-4B8E-8E08-2DBE2803C845}
{FBB3270F-1924-4A72-845E-A6DF39C402F6} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3} = {C564DDD6-DE79-45CD-88EA-3F690481572A}
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41} = {C564DDD6-DE79-45CD-88EA-3F690481572A}
{E8AD265B-3C67-4640-AC58-A522F9FB3361} = {09C264E9-E3F3-4586-9151-DCBB1F6DA7AB}
{C564DDD6-DE79-45CD-88EA-3F690481572A} = {09C264E9-E3F3-4586-9151-DCBB1F6DA7AB}
{50C758FE-4E10-409A-94F5-A75480960864} = {459BF674-83CB-46F6-881F-A2D2117DBF4D}
Expand Down
1 change: 1 addition & 0 deletions Microsoft.Maui-windows.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"src\\Core\\tests\\Benchmarks\\Core.Benchmarks.csproj",
"src\\Core\\tests\\DeviceTests.Shared\\Core.DeviceTests.Shared.csproj",
"src\\Core\\tests\\DeviceTests\\Core.DeviceTests.csproj",
"src\\Core\\tests\\ExternalBackend\\Core.ExternalBackend.csproj",
"src\\Core\\tests\\UnitTests\\Core.UnitTests.csproj",
"src\\Essentials\\samples\\Sample.Server.WebAuthenticator\\Essentials.Sample.Server.WebAuthenticator.csproj",
"src\\Essentials\\samples\\Samples.Server.Passkeys\\Essentials.Samples.Server.Passkeys.csproj",
Expand Down
7 changes: 7 additions & 0 deletions Microsoft.Maui.sln
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtils", "src\TestUtils\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.UnitTests", "src\Core\tests\UnitTests\Core.UnitTests.csproj", "{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.ExternalBackend", "src\Core\tests\ExternalBackend\Core.ExternalBackend.csproj", "{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E8AD265B-3C67-4640-AC58-A522F9FB3361}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C564DDD6-DE79-45CD-88EA-3F690481572A}"
Expand Down Expand Up @@ -298,6 +300,10 @@ Global
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Release|Any CPU.Build.0 = Release|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41}.Release|Any CPU.Build.0 = Release|Any CPU
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -660,6 +666,7 @@ Global
{DAAC2822-63B6-4DE0-83AE-04873CD2F364} = {72397ADB-40A8-4B8E-8E08-2DBE2803C845}
{FBB3270F-1924-4A72-845E-A6DF39C402F6} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3} = {C564DDD6-DE79-45CD-88EA-3F690481572A}
{7A3D1D2E-6C9B-4C0E-8E2E-2E9F1D5B7C41} = {C564DDD6-DE79-45CD-88EA-3F690481572A}
{E8AD265B-3C67-4640-AC58-A522F9FB3361} = {09C264E9-E3F3-4586-9151-DCBB1F6DA7AB}
{C564DDD6-DE79-45CD-88EA-3F690481572A} = {09C264E9-E3F3-4586-9151-DCBB1F6DA7AB}
{50C758FE-4E10-409A-94F5-A75480960864} = {459BF674-83CB-46F6-881F-A2D2117DBF4D}
Expand Down
9 changes: 9 additions & 0 deletions src/Core/src/Handlers/Element/ElementHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ void IElementHandler.DisconnectHandler()
{
if (PlatformView != null && VirtualView != null)
{
// Give derived handlers a chance to tear down state that needs the platform view
// while it is still reachable through the PlatformView property.
OnDisconnecting();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[moderate] Null Safety and Defensive CodingOnDisconnecting() runs before PlatformView is captured/nulled and before DisconnectHandler(oldPlatformView), and it is not exception-guarded. The ViewHandler override now calls into arbitrary RemoveContainer() implementations (including third-party and compatibility renderers) that were previously never invoked on the disconnect path. Concrete failure: ViewHandler<,>.Context on Android throws InvalidOperationException when MauiContext is null, and WrapperView.RemoveContainer(PlatformView, Context, ...) dereferences it; any throw from here aborts the whole disconnect, so VirtualView.Handler is never unlinked, VirtualView/PlatformView are never cleared and _handlerState is never set to Disconnected — the handler is left half-disconnected and the virtual view leaks. Consider wrapping the container unwind so a failure cannot prevent the rest of the disconnect.


// We set the PlatformView to null so no one outside of this handler tries to access
// PlatformView. PlatformView access should be isolated to the instance passed into
// DisconnectHandler
Expand All @@ -151,5 +155,10 @@ void IElementHandler.DisconnectHandler()

_handlerState = ElementHandlerState.Disconnected;
}

// Runs while PlatformView is still set, so overrides can use it to unwind platform state.
private protected virtual void OnDisconnecting()
Comment on lines 144 to +160
{
}
}
}
131 changes: 129 additions & 2 deletions src/Core/src/Handlers/View/ViewHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,18 @@ public virtual bool NeedsContainer
/// <summary>
/// Constructs the <see cref="ContainerView"/> and adds <see cref="PlatformView"/> to a container.
/// </summary>
/// <remarks>This method is called when <see cref="HasContainer"/> is set to <see langword="true"/>.</remarks>
/// <remarks>This method is called when <see cref="HasContainer"/> is set to <see langword="true"/>.
/// An override that cannot reach the <see cref="ContainerView"/> setter - that is, one declared outside of this
/// assembly - should publish the container it created by calling <see cref="SetContainerView(PlatformView?)"/>.</remarks>
protected abstract void SetupContainer();
Comment on lines 144 to 150

/// <summary>
/// Deconstructs the <see cref="ContainerView"/> and removes <see cref="PlatformView"/> from its container.
/// </summary>
/// <remarks>This method is called when <see cref="HasContainer"/> is set to <see langword="false"/>.</remarks>
/// <remarks>This method is called when <see cref="HasContainer"/> is set to <see langword="false"/>.
/// An override that cannot reach the <see cref="ContainerView"/> setter - that is, one declared outside of this
/// assembly - should clear the container it removed by calling <see cref="SetContainerView(PlatformView?)"/> with
/// <see langword="null"/>.</remarks>
protected abstract void RemoveContainer();

/// <summary>
Expand All @@ -159,6 +164,128 @@ public virtual bool NeedsContainer
/// <remarks>Note that this can be <see langword="null"/>. Especially when <see cref="HasContainer"/> is set to <see langword="false"/> this value might not be set.</remarks>
public PlatformView? ContainerView { get; private protected set; }

/// <summary>
/// Sets or clears the view returned by <see cref="ContainerView"/>.
/// </summary>
/// <param name="containerView">The platform view that wraps <see cref="PlatformView"/>, or <see langword="null"/> to clear the current container view.</param>
/// <remarks>
/// <para>This is the supported way for a handler that lives in another assembly - for example a platform backend that
/// ships outside of .NET MAUI - to participate in the container view lifecycle. Call it from
/// <see cref="SetupContainer"/> after the container has been created and the <see cref="PlatformView"/> has been
/// re-parented into it, and call it with <see langword="null"/> from <see cref="RemoveContainer"/> after the
/// <see cref="PlatformView"/> has been moved back to the original parent.</para>
/// <para>This method only records the container view; it never re-parents views, and it does not change
/// <see cref="HasContainer"/>. Attaching and detaching the platform views remains the responsibility of the
/// <see cref="SetupContainer"/> and <see cref="RemoveContainer"/> overrides, which .NET MAUI invokes when
/// <see cref="HasContainer"/> changes.</para>
/// <example>
/// A handler in an external backend assembly:
/// <code language="csharp">
/// public class MyBackendViewHandler&lt;TVirtualView, TPlatformView&gt; : ViewHandler&lt;TVirtualView, TPlatformView&gt;
/// where TVirtualView : class, IView
/// where TPlatformView : MyPlatformView
/// {
/// public override bool NeedsContainer =>
/// VirtualView?.Background is not null ||
/// VirtualView?.Clip is not null ||
/// VirtualView?.Shadow is not null ||
/// base.NeedsContainer;
///
/// protected override void SetupContainer()
/// {
/// if (PlatformView is null || ContainerView is not null)
/// return;
///
/// var wrapper = new MyWrapperView();
/// var parent = PlatformView.Parent;
/// parent?.Remove(PlatformView);
/// wrapper.Content = PlatformView;
/// parent?.Add(wrapper);
///
/// SetContainerView(wrapper);
/// }
///
/// protected override void RemoveContainer()
/// {
/// if (ContainerView is not MyWrapperView wrapper)
/// {
/// SetContainerView(null);
/// return;
/// }
///
/// var parent = wrapper.Parent;
/// parent?.Remove(wrapper);
/// wrapper.Content = null;
/// parent?.Add(PlatformView);
///
/// SetContainerView(null);
/// }
/// }
/// </code>
/// </example>
/// </remarks>
/// <exception cref="System.ArgumentException">Thrown when <paramref name="containerView"/> is not a container type that this handler supports.</exception>
protected void SetContainerView(PlatformView? containerView)
{
if (containerView is not null)
{
ValidateContainerView(containerView);
}

ContainerView = containerView;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[moderate] Public API Surface DesignSetContainerView stores the container without touching HasContainer, so an external backend can end up with ContainerView != null while HasContainer == false (codified by the new test SetContainerViewInstallsAndClearsTheContainerDirectly, which asserts Assert.False(handler.HasContainer) with a live container). In that state the next MapContainerView pass that evaluates NeedsContainer == false short-circuits in the HasContainer setter (if (_hasContainer == value) return;), so RemoveContainer() is never called and the wrapper stays installed and attached for the life of the handler. Either keep the flag in sync when the value transitions between null and non-null, or document/guard that SetContainerView is only legal from inside SetupContainer/RemoveContainer.

}

/// <summary>
/// Validates that <paramref name="containerView"/> is a container type this handler supports, throwing if it is not.
/// </summary>
/// <param name="containerView">The candidate container view. This is never <see langword="null"/>.</param>
/// <remarks>
/// <para>Called by <see cref="SetContainerView(PlatformView?)"/> before the value is stored. Override this when a derived
/// handler shadows <see cref="ContainerView"/> with a more specific type, so that an unsupported container is rejected
/// where the mistake is made instead of surfacing later as an <see cref="System.InvalidCastException"/> from the
/// shadowing getter.</para>
/// <para>The .NET MAUI iOS, Mac Catalyst and Tizen handlers use this to require a
/// <see cref="Platform.WrapperView"/>. An external backend that shadows <see cref="ContainerView"/> with its own
/// wrapper type should do the same.</para>
/// <example>
/// <code language="csharp">
/// public new MyWrapperView? ContainerView => (MyWrapperView?)base.ContainerView;
///
/// protected override void ValidateContainerView(MyPlatformView containerView)
/// {
/// if (containerView is not MyWrapperView)
/// {
/// throw new ArgumentException(
/// $"The container view must be a {nameof(MyWrapperView)}.",
/// nameof(containerView));
/// }
///
/// base.ValidateContainerView(containerView);
/// }
/// </code>
/// </example>
/// </remarks>
/// <exception cref="System.ArgumentException">Thrown by overrides when <paramref name="containerView"/> is not supported.</exception>
protected virtual void ValidateContainerView(PlatformView containerView)
{
}

private protected override void OnDisconnecting()
{
// The platform view is still reachable here, which is the same precondition RemoveContainer()
// runs under during normal operation, so existing overrides stay on their supported path.
// Without this, ContainerView and HasContainer would survive the disconnect and a later
// reconnect would skip SetupContainer() and keep pointing at the stale container.
if (HasContainer)
{
HasContainer = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Handler Mapper and Property Patterns — Setting HasContainer = false here does not just clear state, it invokes the platform RemoveContainer() override during teardown, which re-parents PlatformView back into the container's live parent (iOS oldParent?.InsertSubview(PlatformView, idx) in ViewHandlerOfT.iOS.cs, oldParentChildren?.Insert(idx, PlatformView) on Windows, WrapperView.RemoveContainer on Android). Concrete scenario: Element.DisconnectHandlers() (page unloaded / Shell tab switch) walks a subtree whose platform views are still attached; for every view with Background/Clip/Shadow the wrapper is removed and the now-disconnected raw platform view is inserted into the parent in its place. Nothing removes it afterwards — the parent-side removal path goes through view.ToPlatform(), which no longer resolves to that instance once the handler is disconnected, so the dead view is left rendered in the parent's children. The new test ExternalBackendContainerViewTests.DisconnectingWithAnActiveContainerTearsTheContainerDown asserts exactly this end state (Assert.Same(parent, platformView.Parent)) but only for the in-memory fake backend, where there is no subsequent removal step to break. If the goal is only to prevent stale ContainerView/HasContainer from surviving into a reconnect, clearing the backing field _hasContainer plus ContainerView (without invoking RemoveContainer) achieves that without mutating the view tree at teardown.

}

ContainerView = null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[moderate] Backward Compatibility and Migration — On Tizen this changes the documented dispose order. ViewHandlerOfT.Tizen.cs Dispose(bool) calls (this as IElementHandler)?.DisconnectHandler() and then platformView?.Dispose(); ContainerView?.Dispose();. With OnDisconnecting() now running RemoveContainer(), the Tizen override re-adds PlatformView to parent (containable.Children.Add(PlatformView) / parent?.Add(PlatformView)) and calls PlatformView.UpdateBounds(bounds), and immediately afterwards Dispose() disposes that same platform view — leaving a disposed NUI View parented in the live container. The trailing ContainerView?.Dispose() in Dispose(bool) also becomes dead code because RemoveContainer already disposed and nulled it. No test covers the Tizen dispose path.


base.OnDisconnecting();
}

object? IViewHandler.ContainerView => ContainerView;

/// <summary>
Expand Down
13 changes: 13 additions & 0 deletions src/Core/src/Handlers/View/ViewHandlerOfT.Tizen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ public abstract partial class ViewHandler<TVirtualView, TPlatformView> : IPlatfo
protected set => base.ContainerView = value;
}

/// <inheritdoc/>
protected override void ValidateContainerView(NView containerView)
{
if (containerView is not WrapperView)
{
throw new ArgumentException(
$"The container view must be a {nameof(WrapperView)} because {GetType().Name} exposes {nameof(ContainerView)} as a {nameof(WrapperView)}.",
nameof(containerView));
}

base.ValidateContainerView(containerView);
}

~ViewHandler()
{
Dispose(disposing: false);
Expand Down
14 changes: 14 additions & 0 deletions src/Core/src/Handlers/View/ViewHandlerOfT.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using Microsoft.Maui.Graphics;
using UIKit;

Expand All @@ -11,6 +12,19 @@ public partial class ViewHandler<TVirtualView, TPlatformView> : IPlatformViewHan
protected set => base.ContainerView = value;
}

/// <inheritdoc/>
protected override void ValidateContainerView(UIView containerView)
{
if (containerView is not WrapperView)
{
throw new ArgumentException(
$"The container view must be a {nameof(WrapperView)} because {GetType().Name} exposes {nameof(ContainerView)} as a {nameof(WrapperView)}.",
nameof(containerView));
}

base.ValidateContainerView(containerView);
}

public UIViewController? ViewController { get; set; }

public override void PlatformArrange(Rect rect) =>
Expand Down
2 changes: 2 additions & 0 deletions src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,5 @@ virtual Microsoft.Maui.Platform.StackNavigationManager.OnCreateNavigationAnimati
Microsoft.Maui.ISwipeItemMenuItemIconColor
Microsoft.Maui.ISwipeItemMenuItemIconColor.IconColor.get -> Microsoft.Maui.Graphics.Color?
static Microsoft.Maui.Handlers.SwipeItemMenuItemHandler.MapIconColor(Microsoft.Maui.Handlers.ISwipeItemMenuItemHandler! handler, Microsoft.Maui.ISwipeItemMenuItem! view) -> void
Microsoft.Maui.Handlers.ViewHandler.SetContainerView(Android.Views.View? containerView) -> void
virtual Microsoft.Maui.Handlers.ViewHandler.ValidateContainerView(Android.Views.View! containerView) -> void
3 changes: 3 additions & 0 deletions src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ virtual Microsoft.Maui.Animations.PlatformTicker.Dispose(bool disposing) -> void
Microsoft.Maui.ISwipeItemMenuItemIconColor
Microsoft.Maui.ISwipeItemMenuItemIconColor.IconColor.get -> Microsoft.Maui.Graphics.Color?
static Microsoft.Maui.Handlers.SwipeItemMenuItemHandler.MapIconColor(Microsoft.Maui.Handlers.ISwipeItemMenuItemHandler! handler, Microsoft.Maui.ISwipeItemMenuItem! view) -> void
Microsoft.Maui.Handlers.ViewHandler.SetContainerView(UIKit.UIView? containerView) -> void
virtual Microsoft.Maui.Handlers.ViewHandler.ValidateContainerView(UIKit.UIView! containerView) -> void
override Microsoft.Maui.Handlers.ViewHandler<TVirtualView, TPlatformView>.ValidateContainerView(UIKit.UIView! containerView) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,6 @@ virtual Microsoft.Maui.Animations.PlatformTicker.Dispose(bool disposing) -> void
Microsoft.Maui.ISwipeItemMenuItemIconColor
Microsoft.Maui.ISwipeItemMenuItemIconColor.IconColor.get -> Microsoft.Maui.Graphics.Color?
static Microsoft.Maui.Handlers.SwipeItemMenuItemHandler.MapIconColor(Microsoft.Maui.Handlers.ISwipeItemMenuItemHandler! handler, Microsoft.Maui.ISwipeItemMenuItem! view) -> void
Microsoft.Maui.Handlers.ViewHandler.SetContainerView(UIKit.UIView? containerView) -> void
virtual Microsoft.Maui.Handlers.ViewHandler.ValidateContainerView(UIKit.UIView! containerView) -> void
override Microsoft.Maui.Handlers.ViewHandler<TVirtualView, TPlatformView>.ValidateContainerView(UIKit.UIView! containerView) -> void
3 changes: 3 additions & 0 deletions src/Core/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ static Microsoft.Maui.GridLength.implicit operator Microsoft.Maui.GridLength(str
Microsoft.Maui.ISwipeItemMenuItemIconColor
Microsoft.Maui.ISwipeItemMenuItemIconColor.IconColor.get -> Microsoft.Maui.Graphics.Color?
static Microsoft.Maui.Handlers.SwipeItemMenuItemHandler.MapIconColor(Microsoft.Maui.Handlers.ISwipeItemMenuItemHandler! handler, Microsoft.Maui.ISwipeItemMenuItem! view) -> void
Microsoft.Maui.Handlers.ViewHandler.SetContainerView(Tizen.NUI.BaseComponents.View? containerView) -> void
virtual Microsoft.Maui.Handlers.ViewHandler.ValidateContainerView(Tizen.NUI.BaseComponents.View! containerView) -> void
override Microsoft.Maui.Handlers.ViewHandler<TVirtualView, TPlatformView>.ValidateContainerView(Tizen.NUI.BaseComponents.View! containerView) -> void
Loading
Loading