diff --git a/src/Core/src/Handlers/Window/WindowHandler.iOS.cs b/src/Core/src/Handlers/Window/WindowHandler.iOS.cs index 4025e0b61e58..13e2eedcc629 100644 --- a/src/Core/src/Handlers/Window/WindowHandler.iOS.cs +++ b/src/Core/src/Handlers/Window/WindowHandler.iOS.cs @@ -1,17 +1,38 @@ using System; -using ObjCRuntime; +using Foundation; using UIKit; namespace Microsoft.Maui.Handlers { public partial class WindowHandler : ElementHandler { + readonly WindowProxy _proxy = new(); + protected override void ConnectHandler(UIWindow platformView) { base.ConnectHandler(platformView); - UpdateVirtualViewFrame(platformView); + // For newer Mac Catalyst versions, we want to wait until we get effective window dimensions from the platform. + if (OperatingSystem.IsMacCatalystVersionAtLeast(16)) + { + _proxy.Connect(VirtualView, platformView); + } + else + { + UpdateVirtualViewFrame(platformView); + } + } + + protected override void DisconnectHandler(UIWindow platformView) + { + if (OperatingSystem.IsMacCatalystVersionAtLeast(16)) + { + _proxy.Disconnect(); + } + + base.DisconnectHandler(platformView); } + public static void MapTitle(IWindowHandler handler, IWindow window) => handler.PlatformView.UpdateTitle(window); @@ -23,8 +44,7 @@ public static void MapContent(IWindowHandler handler, IWindow window) handler.PlatformView.RootViewController = nativeContent; - if (window.VisualDiagnosticsOverlay != null) - window.VisualDiagnosticsOverlay.Initialize(); + window.VisualDiagnosticsOverlay?.Initialize(); } public static void MapX(IWindowHandler handler, IWindow view) => @@ -82,12 +102,53 @@ public static void MapMenuBar(IWindowHandler handler, IWindow view) public static void MapRequestDisplayDensity(IWindowHandler handler, IWindow window, object? args) { if (args is DisplayDensityRequest request) + { request.SetResult(handler.PlatformView.GetDisplayDensity()); + } } void UpdateVirtualViewFrame(UIWindow window) { VirtualView.FrameChanged(window.Bounds.ToRectangle()); } + + class WindowProxy + { + WeakReference? _virtualView; + + IWindow? VirtualView => _virtualView is not null && _virtualView.TryGetTarget(out var v) ? v : null; + IDisposable? _effectiveGeometryObserver; + + public void Connect(IWindow virtualView, UIWindow platformView) + { + _virtualView = new(virtualView); + + // https://developer.apple.com/documentation/uikit/uiwindowscene/effectivegeometry?language=objc#Discussion mentions: + // > This property is key-value observing (KVO) compliant. Observing effectiveGeometry is the recommended way + // > to receive notifications of changes to the window scene’s geometry. These changes can occur because of + // > user interaction or as a result of the system resolving a geometry request. + _effectiveGeometryObserver = platformView.WindowScene?.AddObserver("effectiveGeometry", NSKeyValueObservingOptions.OldNew, HandleEffectiveGeometryObserved); + } + + public void Disconnect() + { + _effectiveGeometryObserver?.Dispose(); + } + + void HandleEffectiveGeometryObserved(NSObservedChange obj) + { + if (obj is not null && VirtualView is IWindow virtualView && obj.NewValue is UIWindowSceneGeometry newGeometry) + { + var newRectangle = newGeometry.SystemFrame.ToRectangle(); + + if (double.IsNaN(newRectangle.X) || double.IsNaN(newRectangle.Y) || double.IsNaN(newRectangle.Width) || double.IsNaN(newRectangle.Height)) + { + return; + } + + virtualView.FrameChanged(newRectangle); + } + } + } } } \ No newline at end of file diff --git a/src/Core/src/Hosting/LifecycleEvents/AppHostBuilderExtensions.iOS.cs b/src/Core/src/Hosting/LifecycleEvents/AppHostBuilderExtensions.iOS.cs index a7044fcb97a5..3cd1e1d3bf45 100644 --- a/src/Core/src/Hosting/LifecycleEvents/AppHostBuilderExtensions.iOS.cs +++ b/src/Core/src/Hosting/LifecycleEvents/AppHostBuilderExtensions.iOS.cs @@ -105,21 +105,30 @@ static void OnConfigureWindow(IiOSLifecycleBuilder iOS) { // Pre iOS 13 doesn't support scenes if (!OperatingSystem.IsIOSVersionAtLeast(13)) + { return; + } iOS = iOS .WindowSceneDidUpdateCoordinateSpace((windowScene, _, _, _) => { - if (!OperatingSystem.IsIOSVersionAtLeast(13)) + // Mac Catalyst version 16+ supports effectiveGeometry property on window scenes. + if (!OperatingSystem.IsIOSVersionAtLeast(13) || (OperatingSystem.IsMacCatalystVersionAtLeast(16))) + { return; + } if (windowScene.Delegate is not IUIWindowSceneDelegate wsd || wsd.GetWindow() is not UIWindow platformWindow) + { return; + } var window = platformWindow.GetWindow(); if (window is null) + { return; + } window.FrameChanged(platformWindow.Frame.ToRectangle()); }); diff --git a/src/Core/src/Platform/iOS/WindowExtensions.cs b/src/Core/src/Platform/iOS/WindowExtensions.cs index 67c9607deee3..65e2f745358b 100644 --- a/src/Core/src/Platform/iOS/WindowExtensions.cs +++ b/src/Core/src/Platform/iOS/WindowExtensions.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using CoreGraphics; +using Microsoft.Extensions.Logging; using Microsoft.Maui.Devices; using UIKit; @@ -15,23 +17,46 @@ internal static void UpdateTitle(this UIWindow platformWindow, IWindow window) // If you set it to an empty string the title reverts back to the // default app title. if (OperatingSystem.IsIOSVersionAtLeast(13) && platformWindow.WindowScene is not null) + { platformWindow.WindowScene.Title = window.Title ?? String.Empty; + } } internal static void UpdateX(this UIWindow platformWindow, IWindow window) => - platformWindow.UpdateUnsupportedCoordinate(window); + platformWindow.UpdateCoordinates(window); internal static void UpdateY(this UIWindow platformWindow, IWindow window) => - platformWindow.UpdateUnsupportedCoordinate(window); + platformWindow.UpdateCoordinates(window); internal static void UpdateWidth(this UIWindow platformWindow, IWindow window) => - platformWindow.UpdateUnsupportedCoordinate(window); + platformWindow.UpdateCoordinates(window); internal static void UpdateHeight(this UIWindow platformWindow, IWindow window) => - platformWindow.UpdateUnsupportedCoordinate(window); + platformWindow.UpdateCoordinates(window); - internal static void UpdateUnsupportedCoordinate(this UIWindow platformWindow, IWindow window) => - window.FrameChanged(platformWindow.Bounds.ToRectangle()); + internal static void UpdateCoordinates(this UIWindow platformWindow, IWindow window) + { + if (OperatingSystem.IsMacCatalyst() && OperatingSystem.IsIOSVersionAtLeast(16) && platformWindow.WindowScene is {} windowScene) + { + if (double.IsNaN(window.X) || double.IsNaN(window.Y) || double.IsNaN(window.Width) || double.IsNaN(window.Height)) + { + return; + } + + var preferences = new UIWindowSceneGeometryPreferencesMac() + { + SystemFrame = new CGRect(window.X, window.Y, window.Width, window.Height) + }; + + windowScene.RequestGeometryUpdate(preferences, (error) => { + window.Handler?.MauiContext?.CreateLogger()?.LogError("Requesting geometry update failed with error '{error}'.", error); + }); + } + else + { + window.FrameChanged(platformWindow.Bounds.ToRectangle()); + } + } public static void UpdateMaximumWidth(this UIWindow platformWindow, IWindow window) => platformWindow.UpdateMaximumSize(window.MaximumWidth, window.MaximumHeight); @@ -45,7 +70,9 @@ public static void UpdateMaximumSize(this UIWindow platformWindow, IWindow windo internal static void UpdateMaximumSize(this UIWindow platformWindow, double width, double height) { if (!OperatingSystem.IsIOSVersionAtLeast(13)) + { return; + } var restrictions = platformWindow.WindowScene?.SizeRestrictions; if (restrictions is null) @@ -71,7 +98,9 @@ public static void UpdateMinimumSize(this UIWindow platformWindow, IWindow windo internal static void UpdateMinimumSize(this UIWindow platformWindow, double width, double height) { if (!OperatingSystem.IsIOSVersionAtLeast(13)) + { return; + } var restrictions = platformWindow.WindowScene?.SizeRestrictions; if (restrictions is null) diff --git a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt index a41f800b4428..d7c0c90a8422 100644 --- a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -80,3 +80,4 @@ override Microsoft.Maui.Platform.MauiCALayer.Dispose(bool disposing) -> void override Microsoft.Maui.Platform.MauiCALayer.RemoveFromSuperLayer() -> void override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void override Microsoft.Maui.Handlers.EditorHandler.NeedsContainer.get -> bool +override Microsoft.Maui.Handlers.WindowHandler.DisconnectHandler(UIKit.UIWindow! platformView) -> void diff --git a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index a41f800b4428..d7c0c90a8422 100644 --- a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -80,3 +80,4 @@ override Microsoft.Maui.Platform.MauiCALayer.Dispose(bool disposing) -> void override Microsoft.Maui.Platform.MauiCALayer.RemoveFromSuperLayer() -> void override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void override Microsoft.Maui.Handlers.EditorHandler.NeedsContainer.get -> bool +override Microsoft.Maui.Handlers.WindowHandler.DisconnectHandler(UIKit.UIWindow! platformView) -> void