diff --git a/maui/src/Core/WindowOverlay/WindowOverlay.Android.cs b/maui/src/Core/WindowOverlay/WindowOverlay.Android.cs index 1540a286..e97aa0a9 100644 --- a/maui/src/Core/WindowOverlay/WindowOverlay.Android.cs +++ b/maui/src/Core/WindowOverlay/WindowOverlay.Android.cs @@ -55,16 +55,24 @@ internal partial class SfWindowOverlay /// internal virtual WindowOverlayStack CreateStack(Context context) { + // Prefer creating the overlay stack via the MAUI handler so gesture/touch plumbing is wired. WindowOverlayStack? windowOverlayStack = null; + if (_window is not null && _window.Handler is not null) { IMauiContext? mauiContext = _window.Handler.MauiContext; if (mauiContext is not null) { - windowOverlayStack = (WindowOverlayStack?)_overlayStackView?.ToPlatform(mauiContext); + windowOverlayStack = _overlayStackView != null ? (WindowOverlayStack?)_overlayStackView.ToPlatform(mauiContext) : null; } } + // Fallback to HostMauiContext for native embedding (no MAUI Window). + if (windowOverlayStack == null && WindowOverlayHelper.HostMauiContext != null) + { + windowOverlayStack = _overlayStackView != null ? (WindowOverlayStack?)_overlayStackView.ToPlatform(WindowOverlayHelper.HostMauiContext) : null; + } + return windowOverlayStack is not null ? windowOverlayStack : new WindowOverlayStack(context); } @@ -251,6 +259,33 @@ internal bool AddToOverlay(MauiView childView) } } } + else + { + // Native embedding path: no MAUI Window, use host Activity and IMauiContext set by the embedding app. + var activity = WindowOverlayHelper.HostActivity; + _density = WindowOverlayHelper._density; + _rootView = WindowOverlayHelper._platformRootView; + if (_rootView != null && activity != null) + { + _overlayStack = CreateStack(activity); + _windowManager = activity.WindowManager; + if (_overlayStack != null) + { + _overlayStack.LayoutChange += OnOverlayStackLayoutChange; + if (this._windowManagerLayoutParams == null) + { + this.GetWindowManagerLayoutParams(); + } + + // Prefer an existing native view if handler already realized. + if (WindowOverlayHelper.HostMauiContext != null) + { + _overlayContent = childView.ToPlatform(WindowOverlayHelper.HostMauiContext); + return true; + } + } + } + } return false; } diff --git a/maui/src/Core/WindowOverlay/WindowOverlay.iOS.cs b/maui/src/Core/WindowOverlay/WindowOverlay.iOS.cs index 9ad5c48d..edfd15f7 100644 --- a/maui/src/Core/WindowOverlay/WindowOverlay.iOS.cs +++ b/maui/src/Core/WindowOverlay/WindowOverlay.iOS.cs @@ -10,6 +10,10 @@ internal partial class SfWindowOverlay { #region Fields + internal static IMauiContext? MauiContext { get; set; } + + internal static UIWindow? Window { get; set; } + UIView? _rootView; WindowOverlayStack? _overlayStack; UIView? _overlayContent; @@ -23,23 +27,21 @@ internal partial class SfWindowOverlay internal virtual WindowOverlayStack CreateStack() { WindowOverlayStack? windowOverlayStack = null; - if (_window is not null && _window.Handler is not null) + IMauiContext? context = _window?.Handler?.MauiContext ?? SfWindowOverlay.MauiContext; + if (context is not null) { - IMauiContext? context = _window.Handler.MauiContext; - if (context is not null) - { - windowOverlayStack = (WindowOverlayStack?)_overlayStackView?.ToPlatform(context); + windowOverlayStack = (WindowOverlayStack?)_overlayStackView?.ToPlatform(context); - if (windowOverlayStack is not null && _overlayStackView is not null) - { - windowOverlayStack._canHandleTouch = !_overlayStackView.canHandleTouch; - } + if (windowOverlayStack is not null && _overlayStackView is not null) + { + windowOverlayStack._canHandleTouch = !_overlayStackView.canHandleTouch; } } - return windowOverlayStack is not null ? windowOverlayStack : new WindowOverlayStack(); + return windowOverlayStack is not null ? windowOverlayStack : new WindowOverlayStack(); } + /// /// Adds or updates the child layout absolutely to the overlay stack. /// @@ -171,6 +173,22 @@ internal void RemoveOverlay() internal bool AddToOverlay(MauiView childView) { _window = WindowOverlayHelper._window; + + // Native embedding path when MAUI Window is not yet available. + if ((_window == null || _window.Content == null) && SfWindowOverlay.MauiContext != null && SfWindowOverlay.Window != null) + { + _rootView = SfWindowOverlay.Window; + _overlayStack ??= CreateStack(); + if (_overlayStack != null) + { + _overlayStack.AutoresizingMask = UIViewAutoresizing.All; + _overlayContent = childView.ToPlatform(SfWindowOverlay.MauiContext); + return true; + } + + return false; + } + if (_window is not null && _window.Content is not null) { _rootView = WindowOverlayHelper._platformRootView; @@ -260,7 +278,9 @@ internal void PositionOverlayContent(double x = 0, double y = 0) keyWindow = UIApplication.SharedApplication.KeyWindow!; } - if (_rootView != keyWindow) + // Only switch to the keyWindow if we actually found one. For native embedding scenarios. + // the initial rootView may be provided via SfWindowOverlay.Window and keyWindow can be null. + if (keyWindow != null && _rootView != keyWindow) { _rootView = keyWindow; } diff --git a/maui/src/Core/WindowOverlay/WindowOverlayHelper.cs b/maui/src/Core/WindowOverlay/WindowOverlayHelper.cs index 09efba2e..eea1ac34 100644 --- a/maui/src/Core/WindowOverlay/WindowOverlayHelper.cs +++ b/maui/src/Core/WindowOverlay/WindowOverlayHelper.cs @@ -39,7 +39,11 @@ internal static IWindow? _window /// /// Gets the device density. /// - internal static float _density => _window is not null ? _window.RequestDisplayDensity() : 1f; +#if ANDROID + internal static float _density => _window != null ? _window.RequestDisplayDensity() : (HostActivity != null ? HostActivity.Resources?.DisplayMetrics?.Density ?? 1f : 1f); +#else + internal static float _density => _window != null ? _window.RequestDisplayDensity() : 1f; +#endif /// /// Gets the root view of the device. @@ -48,6 +52,16 @@ internal static IWindow? _window #if ANDROID + /// + /// Optional host Activity for embedding scenarios where MAUI Window is not available. + /// + internal static Activity? HostActivity; + + /// + /// Optional host IMauiContext for embedding scenarios when converting views without a Window. + /// + internal static IMauiContext? HostMauiContext; + /// /// Gets the decor view frame. /// @@ -155,6 +169,20 @@ internal static IWindow? _window } #endif } +#if ANDROID + // Fallback for native embedding: derive root from host Activity when MAUI Window is unavailable. + else if (rootView == null && HostActivity != null) + { + try + { + rootView = HostActivity.FindViewById(Android.Resource.Id.Content) as ViewGroup; + } + catch + { + rootView = null; + } + } +#endif return rootView; } @@ -178,6 +206,17 @@ internal static IWindow? _window return platformActivity.Window; } + // Fallback for native embedding: use HostActivity if available. + else if (HostActivity != null) + { + if (HostActivity.WindowManager != null && HostActivity.WindowManager.DefaultDisplay != null) + { + return HostActivity.Window; + } + + return null; + } + return null; } diff --git a/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Android.cs b/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Android.cs index fea63751..348f8b14 100644 --- a/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Android.cs +++ b/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Android.cs @@ -474,7 +474,17 @@ internal static Rect GetRelativeViewBounds(this SfPopup popup, MauiView relative { int[] relativeViewOrigin = new int[2]; nativeRelativeView.GetLocationInWindow(relativeViewOrigin); - relativeViewOrigin[1] = PopupExtension.GetLocationInApp(relativeView); + + if (WindowOverlayHelper._decorViewContent is PlatformView decorView && nativeRelativeView.RootView == decorView) + { + relativeViewOrigin[1] = PopupExtension.GetLocationInApp(relativeView); + } + else + { + // 1008572: Popup relative position is incorrect when opening a popup within another popup on Android. + relativeViewOrigin[1] = (int)(relativeViewOrigin[1] / WindowOverlayHelper._density); + } + return new Rect(relativeViewOrigin[0] / WindowOverlayHelper._density, relativeViewOrigin[1], nativeRelativeView.Width / WindowOverlayHelper._density, nativeRelativeView.Height / WindowOverlayHelper._density); } else diff --git a/maui/src/Popup/Helpers/PopupExtension/PopupExtension.iOS.cs b/maui/src/Popup/Helpers/PopupExtension/PopupExtension.iOS.cs index e6360e13..1f716d05 100644 --- a/maui/src/Popup/Helpers/PopupExtension/PopupExtension.iOS.cs +++ b/maui/src/Popup/Helpers/PopupExtension/PopupExtension.iOS.cs @@ -22,7 +22,21 @@ internal static partial class PopupExtension internal static int GetScreenWidth() { var rootView = WindowOverlayHelper._platformRootView; - return rootView is not null ? (int)rootView.Frame.Width : 0; + if (rootView is not null) + { + return (int)rootView.Frame.Width; + } + + // Native embedding: Get width from the underlying UIWindow. + var window = (WindowOverlayHelper._window?.ToPlatform() as UIWindow) ?? GetActiveWindow(); + if (window is not null) + { + return (int)window.Bounds.Width; + } + + // use UIScreen.MainScreen width (iOS-specific). + var screen = UIScreen.MainScreen; + return screen is not null ? (int)screen.Bounds.Width : 0; } /// @@ -32,7 +46,21 @@ internal static int GetScreenWidth() internal static int GetScreenHeight() { var rootView = WindowOverlayHelper._platformRootView; - return rootView is not null ? (int)rootView.Frame.Height : 0; + if (rootView is not null) + { + return (int)rootView.Frame.Height; + } + + // Native embedding: Get height from the underlying UIWindow. + var window = (WindowOverlayHelper._window?.ToPlatform() as UIWindow) ?? GetActiveWindow(); + if (window is not null) + { + return (int)window.Bounds.Height; + } + + // use UIScreen.MainScreen height (iOS-specific). + var screen = UIScreen.MainScreen; + return screen is not null ? (int)screen.Bounds.Height : 0; } /// @@ -81,6 +109,43 @@ internal static int GetY(this MauiView view) /// Returns the status bar height. internal static double GetStatusBarHeight() { + // Try to obtain the active platform window (supports native embedding scenarios). + var platformWindow = (WindowOverlayHelper._window?.ToPlatform() as UIWindow) ?? GetActiveWindow(); + + if (platformWindow is not null && WindowOverlayHelper._window is null) + { + try + { + // For iOS 13+ with scenes, use the StatusBarManager when available. + if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0)) + { + var scene = platformWindow.WindowScene; + if (scene is not null) + { +#if IOS || MACCATALYST + var statusBarManager = scene.StatusBarManager; + if (statusBarManager is not null) + { + return statusBarManager.StatusBarFrame.Height; + } +#endif + } + } + + // Fallback to the top safe area inset if present. + if (platformWindow.SafeAreaInsets.Top > 0) + { + return platformWindow.SafeAreaInsets.Top; + } + } + catch + { + // ignore and return 0 as final fallback. + } + + return 0; + } + return 0; } @@ -90,7 +155,7 @@ internal static double GetStatusBarHeight() /// Returns action bar height. internal static int GetActionBarHeight() { - var platformWindow = WindowOverlayHelper._window?.ToPlatform() as UIWindow; + var platformWindow = (WindowOverlayHelper._window?.ToPlatform() as UIWindow) ?? GetActiveWindow(); // To calculate the navigationBar height with the page Y position. if (GetMainPage() is Page page && page.Handler is not null && page.Handler.PlatformView is UIView pageNativeView && platformWindow is not null) @@ -120,53 +185,138 @@ internal static int GetActionBarHeight() internal static int GetSafeAreaHeight(string position) { // The popup is drawn within the safe area when shown relative to a view. Safe area calculations apply only to the iOS X simulator on iOS. -#pragma warning disable CS0618 // Suppressing CS0618 warning because Page.GetUseSafeArea is marked obsolete in .NET 10. - if (Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.Page.GetUseSafeArea(GetMainPage())) -#pragma warning restore CS0618 - { - var platformWindow = WindowOverlayHelper._window?.ToPlatform() as UIWindow; - UIInterfaceOrientation? statusBarOrientation = null; - var scene = platformWindow?.WindowScene; - if (scene != null) + if (WindowOverlayHelper._window != null) + { + // PopUp is drawn in safe area also crap when it is shown based on relative to view point. + // Fix Details: SafeArea is calculated only for iOS X simulator in iOS. +#if NET10_0 + if (GetSafeAreaEdges()) +#else + if (Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.Page.GetUseSafeArea(GetMainPage())) +#endif { -#if IOS || MACCATALYST - if (OperatingSystem.IsIOSVersionAtLeast(16, 0) || OperatingSystem.IsMacCatalystVersionAtLeast(16, 0)) - { - statusBarOrientation = scene.EffectiveGeometry.InterfaceOrientation; - } - else + var platformWindow = WindowOverlayHelper._window?.ToPlatform() as UIWindow; + UIInterfaceOrientation? statusBarOrientation = null; + var scene = platformWindow?.WindowScene; + if (scene != null) { +#if IOS || MACCATALYST + if (OperatingSystem.IsIOSVersionAtLeast(16, 0) || OperatingSystem.IsMacCatalystVersionAtLeast(16, 0)) + { + statusBarOrientation = scene.EffectiveGeometry.InterfaceOrientation; + } + else + { #pragma warning disable CA1422 // InterfaceOrientation is obsoleted on newer SDKs; guarded by OS version checks - statusBarOrientation = scene.InterfaceOrientation; + statusBarOrientation = scene.InterfaceOrientation; #pragma warning restore CA1422 - } + } #endif - } + } - // Since StatusBarHeight is already calculated, the top safe area does not need to be computed. - if (position == "Top") - { - return platformWindow is not null ? (int)platformWindow.SafeAreaInsets.Top : 0; - } - else if ((statusBarOrientation == UIInterfaceOrientation.Portrait || statusBarOrientation == UIInterfaceOrientation.PortraitUpsideDown || statusBarOrientation == UIInterfaceOrientation.LandscapeLeft || statusBarOrientation == UIInterfaceOrientation.LandscapeRight || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceUp || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceDown) && position == "Bottom") - { - return platformWindow?.SafeAreaInsets.Bottom > 0 ? (int)platformWindow.SafeAreaInsets.Bottom : 0; - } - else if ((UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceUp || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceDown) && position == "Right" && GetScreenWidth() > GetScreenHeight()) - { - return platformWindow?.SafeAreaInsets.Right > 0 ? (int)platformWindow.SafeAreaInsets.Right : 0; - } - else if ((UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceUp || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceDown) && (position == "Left") && GetScreenWidth() > GetScreenHeight()) - { - return platformWindow?.SafeAreaInsets.Left > 0 ? (int)platformWindow.SafeAreaInsets.Left : 0; - } - else if (statusBarOrientation == UIInterfaceOrientation.LandscapeLeft) - { - return platformWindow?.SafeAreaInsets.Left > 0 ? (int)platformWindow.SafeAreaInsets.Left : 0; + // Since StatusBarHeight is already calculated, the top safe area does not need to be computed. + if (position == "Top") + { + return platformWindow is not null ? (int)platformWindow.SafeAreaInsets.Top : 0; + } + else if ((statusBarOrientation == UIInterfaceOrientation.Portrait || statusBarOrientation == UIInterfaceOrientation.PortraitUpsideDown || statusBarOrientation == UIInterfaceOrientation.LandscapeLeft || statusBarOrientation == UIInterfaceOrientation.LandscapeRight || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceUp || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceDown) && position == "Bottom") + { + return platformWindow?.SafeAreaInsets.Bottom > 0 ? (int)platformWindow.SafeAreaInsets.Bottom : 0; + } + else if ((UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceUp || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceDown) && position == "Right" && GetScreenWidth() > GetScreenHeight()) + { + return platformWindow?.SafeAreaInsets.Right > 0 ? (int)platformWindow.SafeAreaInsets.Right : 0; + } + else if ((UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceUp || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceDown) && (position == "Left") && GetScreenWidth() > GetScreenHeight()) + { + return platformWindow?.SafeAreaInsets.Left > 0 ? (int)platformWindow.SafeAreaInsets.Left : 0; + } + else if (statusBarOrientation == UIInterfaceOrientation.LandscapeLeft) + { + return platformWindow?.SafeAreaInsets.Left > 0 ? (int)platformWindow.SafeAreaInsets.Left : 0; + } + else if (statusBarOrientation == UIInterfaceOrientation.LandscapeRight) + { + return platformWindow?.SafeAreaInsets.Right > 0 ? (int)platformWindow.SafeAreaInsets.Right : 0; + } } - else if (statusBarOrientation == UIInterfaceOrientation.LandscapeRight) + } + else + { + if (GetSafeAreaEdges()) { - return platformWindow?.SafeAreaInsets.Right > 0 ? (int)platformWindow.SafeAreaInsets.Right : 0; + // Prefer the platform root view's safe area in native embedding, then the platform window. + var rootView = WindowOverlayHelper._platformRootView as UIView + ?? WindowOverlayHelper._window?.ToPlatform() as UIView + ?? PopupExtension.GetActiveWindow() as UIView; + + var platformWindow = (WindowOverlayHelper._window?.ToPlatform() as UIWindow) ?? GetActiveWindow(); + + UIInterfaceOrientation? statusBarOrientation = null; + var scene = platformWindow?.WindowScene; + if (scene != null) + { +#if IOS || MACCATALYST + if (OperatingSystem.IsIOSVersionAtLeast(16, 0) || OperatingSystem.IsMacCatalystVersionAtLeast(16, 0)) + { + statusBarOrientation = scene.EffectiveGeometry.InterfaceOrientation; + } + else + { +#pragma warning disable CA1422 // InterfaceOrientation is obsoleted on newer SDKs; guarded by OS version checks. + statusBarOrientation = scene.InterfaceOrientation; +#pragma warning restore CA1422 + } +#endif + } + + // Top: prefer rootView inset, then window inset, then status bar height fallback. + if (position == "Top") + { + if (rootView != null && rootView.SafeAreaInsets.Top > 0) + { + return (int)rootView.SafeAreaInsets.Top; + } + + if (platformWindow != null && platformWindow.SafeAreaInsets.Top > 0) + { + return (int)platformWindow.SafeAreaInsets.Top; + } + + return (int)GetStatusBarHeight(); + } + + // Bottom: prefer rootView inset then window inset. + if (position == "Bottom") + { + if (rootView != null && rootView.SafeAreaInsets.Bottom > 0) + { + return (int)rootView.SafeAreaInsets.Bottom; + } + + return platformWindow?.SafeAreaInsets.Bottom > 0 ? (int)platformWindow.SafeAreaInsets.Bottom : 0; + } + + // Left/Right: consider device orientation and prefer rootView then window. + if (position == "Left") + { + if (rootView != null && rootView.SafeAreaInsets.Left > 0) + { + return (int)rootView.SafeAreaInsets.Left; + } + + return platformWindow?.SafeAreaInsets.Left > 0 ? (int)platformWindow.SafeAreaInsets.Left : 0; + } + + if (position == "Right") + { + if (rootView != null && rootView.SafeAreaInsets.Right > 0) + { + return (int)rootView.SafeAreaInsets.Right; + } + + return platformWindow?.SafeAreaInsets.Right > 0 ? (int)platformWindow.SafeAreaInsets.Right : 0; + } } } @@ -250,9 +400,12 @@ internal static Rect GetRelativeViewBounds(this SfPopup popup, MauiView relative if (relativeView.Handler != null && relativeView.Handler.PlatformView != null && relativeView.Handler.PlatformView is PlatformView nativeRelativeView && WindowOverlayHelper._platformRootView != null) { - PlatformView rootView = WindowOverlayHelper._platformRootView; - nfloat[] location = new nfloat[2]; - CGPoint relativeViewOrigin = nativeRelativeView.ConvertPointToView(new CGPoint(0, 0), rootView); + PlatformView? rootView = WindowOverlayHelper._platformRootView as UIView + ?? WindowOverlayHelper._window?.ToPlatform() as UIView + ?? PopupExtension.GetActiveWindow() as UIView; + CGPoint relativeViewOrigin = rootView != null + ? nativeRelativeView.ConvertPointToView(new CGPoint(0, 0), rootView) + : nativeRelativeView.ConvertPointToView(new CGPoint(0, 0), null); return new Rect(relativeViewOrigin.X, relativeViewOrigin.Y, nativeRelativeView.Frame.Width, nativeRelativeView.Frame.Height); } else @@ -261,6 +414,88 @@ internal static Rect GetRelativeViewBounds(this SfPopup popup, MauiView relative } } + /// + /// This method will returns the SafeAreEdges of the Content page. + /// + /// Returns the whether the SafeAreaEdges is set for the page. + internal static bool GetSafeAreaEdges() + { + if (WindowOverlayHelper._window != null) + { +#if NET10_0 + if (GetMainPage() != null && GetMainPage() is ContentPage page && page.SafeAreaEdges != SafeAreaEdges.None) + { + return true; + } +#endif + } + else + { + var rootView = WindowOverlayHelper._platformRootView; + if (rootView != null) + { + if (rootView.SafeAreaInsets.Top > 0 || rootView.SafeAreaInsets.Bottom > 0 || rootView.SafeAreaInsets.Left > 0 || rootView.SafeAreaInsets.Right > 0) + { + return true; + } + } + + var platformWindow = (WindowOverlayHelper._window?.ToPlatform() as UIWindow) ?? GetActiveWindow(); + if (platformWindow != null) + { + if (platformWindow.SafeAreaInsets.Top > 0 || platformWindow.SafeAreaInsets.Bottom > 0 || platformWindow.SafeAreaInsets.Left > 0 || platformWindow.SafeAreaInsets.Right > 0) + { + return true; + } + } + } + + return false; + } + + /// + /// Attempts to get the active UIWindow for the application, including iOS 13+ multi-scene setups. + /// + /// The active UIWindow if available; otherwise null. + internal static UIWindow? GetActiveWindow() + { + try + { + if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0)) + { + var app = UIApplication.SharedApplication; + foreach (var scene in app.ConnectedScenes) + { + UIWindow? keyWindow = null; + if (scene is UIWindowScene windowScene) + { + keyWindow = windowScene.Windows?.FirstOrDefault(w => w.IsKeyWindow); + if (keyWindow is not null) + { + return keyWindow; + } + + keyWindow = windowScene.Windows?.FirstOrDefault(); + if (keyWindow is not null) + { + return keyWindow; + } + } + } + } + +#pragma warning disable CA1422 // Fallback for pre-iOS 13 or if scenes are not available. + return UIApplication.SharedApplication?.KeyWindow + ?? UIApplication.SharedApplication?.Windows?.FirstOrDefault(w => w.IsKeyWindow) + ?? UIApplication.SharedApplication?.Windows?.FirstOrDefault(); +#pragma warning disable + } + catch + { + return null; + } + } + #endregion #region Private Methods diff --git a/maui/src/Popup/PopupFooter.cs b/maui/src/Popup/PopupFooter.cs index f0bb340e..a435d177 100644 --- a/maui/src/Popup/PopupFooter.cs +++ b/maui/src/Popup/PopupFooter.cs @@ -73,6 +73,14 @@ internal class PopupFooter : SfView /// public PopupFooter() { +#if IOS + // 916944 : When setting SafeArea for a page, the value does not update within the popup content due to a framework issue. +#if NET10_0 + this.IgnoreSafeArea = !PopupExtension.GetSafeAreaEdges(); +#else + this.IgnoreSafeArea = !Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.Page.GetUseSafeArea(PopupExtension.GetMainPage()); +#endif +#endif } /// diff --git a/maui/src/Popup/PopupHeader.cs b/maui/src/Popup/PopupHeader.cs index 1f518879..7ac90d70 100644 --- a/maui/src/Popup/PopupHeader.cs +++ b/maui/src/Popup/PopupHeader.cs @@ -57,6 +57,14 @@ internal class PopupHeader : SfView /// public PopupHeader() { +#if IOS + // 895700 : When Page SafeArea is false, close icon overlaps header.Because HeaderView arranging with safeArea. +#if NET10_0 + this.IgnoreSafeArea = !PopupExtension.GetSafeAreaEdges(); +#else + this.IgnoreSafeArea = !Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.Page.GetUseSafeArea(PopupExtension.GetMainPage()); +#endif +#endif Initialize(); } diff --git a/maui/src/Popup/PopupMessageView.cs b/maui/src/Popup/PopupMessageView.cs index 3e8a54f7..565edc1d 100644 --- a/maui/src/Popup/PopupMessageView.cs +++ b/maui/src/Popup/PopupMessageView.cs @@ -50,6 +50,14 @@ internal class PopupMessageView : SfContentView /// public PopupMessageView() { +#if IOS + // 916944 : When setting SafeArea for a page, the value does not update within the popup content due to a framework issue. +#if NET10_0 + this.IgnoreSafeArea = !PopupExtension.GetSafeAreaEdges(); +#else + this.IgnoreSafeArea = !Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.Page.GetUseSafeArea(PopupExtension.GetMainPage()); +#endif +#endif } /// diff --git a/maui/src/Popup/SfPopup/SfPopup.Android.cs b/maui/src/Popup/SfPopup/SfPopup.Android.cs index 4726d391..392cb17b 100644 --- a/maui/src/Popup/SfPopup/SfPopup.Android.cs +++ b/maui/src/Popup/SfPopup/SfPopup.Android.cs @@ -45,6 +45,26 @@ public partial class SfPopup #endregion + #region Constructor + + /// + /// Initializes a new instance of the class for native Android embedding scenarios where a MAUI Window is not available. + /// + /// The host that provides the necessary Android context when embedding the popup in a native application. + /// The host that provides access to MAUI-specific services when embedding the SfPopup in a native Android scenario. + public SfPopup(Activity activity, IMauiContext mauiContext) + : this() + { + // Register the host Activity and optional IMauiContext for native embedding. + Syncfusion.Maui.Toolkit.Internals.WindowOverlayHelper.HostActivity = activity; + if (mauiContext != null) + { + Syncfusion.Maui.Toolkit.Internals.WindowOverlayHelper.HostMauiContext = mauiContext; + } + } + + #endregion + #region Internal Methods /// @@ -64,6 +84,21 @@ internal void WirePlatformSpecificEvents() } } } + else if (WindowOverlayHelper._window == null) + { + // Native Embedding: MainPage is null. Use the platform root or decor view to wire GlobalLayout. + // WindowOverlayHelper is initialized via SfPopup(Activity, IMauiContext) constructor in embedding scenarios. + var nativeView = WindowOverlayHelper._platformRootView as PlatformView + ?? WindowOverlayHelper._decorViewContent as PlatformView; + + if (nativeView != null && nativeView.ViewTreeObserver != null) + { + nativeView.ViewTreeObserver.GlobalLayout += this.OnViewTreeObserverGlobalLayout; + } + + // In native embedding, handle orientation and display changes by listening to . + DeviceDisplay.MainDisplayInfoChanged += this.OnViewTreeObserverGlobalLayout; + } } /// @@ -83,6 +118,19 @@ internal void UnWirePlatformSpecificEvents() } } } + else if (WindowOverlayHelper._window == null) + { + var nativeView = WindowOverlayHelper._platformRootView as PlatformView + ?? WindowOverlayHelper._decorViewContent as PlatformView; + + if (nativeView != null && nativeView.ViewTreeObserver != null) + { + nativeView.ViewTreeObserver.GlobalLayout -= this.OnViewTreeObserverGlobalLayout; + } + + // Unwire orientation/display change handler. + DeviceDisplay.MainDisplayInfoChanged -= this.OnViewTreeObserverGlobalLayout; + } } /// @@ -90,7 +138,7 @@ internal void UnWirePlatformSpecificEvents() /// internal void PopupPositionBasedOnKeyboard() { - if (_popupView is not null) + if (this._popupView is not null) { _popupView.HandlerChanged += OnPopupViewHandlerChanged; } diff --git a/maui/src/Popup/SfPopup/SfPopup.iOS.cs b/maui/src/Popup/SfPopup/SfPopup.iOS.cs index aa9c65eb..f45c3e17 100644 --- a/maui/src/Popup/SfPopup/SfPopup.iOS.cs +++ b/maui/src/Popup/SfPopup/SfPopup.iOS.cs @@ -3,6 +3,7 @@ using Foundation; using Microsoft.Maui.Controls.Platform; using Microsoft.Maui.Platform; +using Syncfusion.Maui.Toolkit.Internals; using UIKit; namespace Syncfusion.Maui.Toolkit.Popup @@ -45,6 +46,24 @@ internal Page? ModalPage #endregion + #region Constructor + + /// + /// Initializes a new instance of the class for native iOS embedding scenarios where a MAUI Window is not available. + /// + /// The external that hosts the popup when no MAUI Window exists. + /// The providing access to MAUI services in native iOS embedding. + public SfPopup(UIWindow window, IMauiContext mauiContext) + : this() + { + Syncfusion.Maui.Toolkit.Internals.SfWindowOverlay.MauiContext = mauiContext; + Syncfusion.Maui.Toolkit.Internals.SfWindowOverlay.Window = window; + + // Try initializing overlay again with external host available. + this.InitializeOverlay(); + } + #endregion + #region Internal Methods /// @@ -67,7 +86,10 @@ internal void WirePlatformSpecificEvents() } else { - windowPage.SizeChanged += OnMainPageSizeChanged; + windowPage.SizeChanged += this.OnMainPageSizeChanged; + + // In native embedding, handle orientation and display changes by listening to . + DeviceDisplay.MainDisplayInfoChanged += this.OnMainPageSizeChanged; } } @@ -94,7 +116,10 @@ internal void UnWirePlatformSpecificEvents() } else { - windowPage.SizeChanged -= OnMainPageSizeChanged; + windowPage.SizeChanged -= this.OnMainPageSizeChanged; + + // Unwire orientation/display change handler. + DeviceDisplay.MainDisplayInfoChanged -= this.OnMainPageSizeChanged; } } @@ -206,7 +231,7 @@ void OnMainPageSizeChanged(object? sender, EventArgs e) SyncPopupDimensionFields(); // While show the popup using ShowRelativeToView, Popup is not positioned properly when resize the window in MAC and Split the screen in iOS. - if (_relativeView is not null) + if (_relativeView is not null || this.RelativeView is not null) { Dispatcher.Dispatch(ResetPopupWidthHeight); }