File: Handlers\Window\WindowHandler.iOS.cs
Web Access
Project: src\src\Core\src\Core.csproj (Microsoft.Maui)
using System;
using Foundation;
using UIKit;
 
namespace Microsoft.Maui.Handlers
{
	public partial class WindowHandler : ElementHandler<IWindow, UIWindow>
	{
		readonly WindowProxy _proxy = new();
 
		protected override void ConnectHandler(UIWindow platformView)
		{
			base.ConnectHandler(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);
 
#if MACCATALYST
		internal static void MapTitleBar(IWindowHandler handler, IWindow window)
		{
			if (handler.PlatformView.RootViewController is WindowViewController controller && handler?.MauiContext is IMauiContext mauiContext)
			{
				controller.SetUpTitleBar(window, mauiContext);
			}
		}
#endif
 
		public static void MapContent(IWindowHandler handler, IWindow window)
		{
			_ = handler.MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class.");
 
			var nativeContent = window.Content.ToUIViewController(handler.MauiContext);
 
			var rootViewController = handler.PlatformView.RootViewController;
 
#if MACCATALYST
			// If we are in Catalyst, use the WindowViewController so we can handle the TitleBar
			handler.PlatformView.RootViewController = new WindowViewController(nativeContent, window, handler.MauiContext);
#else
			handler.PlatformView.RootViewController = nativeContent;
#endif
 
			window.VisualDiagnosticsOverlay?.Initialize();
		}
 
		public static void MapX(IWindowHandler handler, IWindow view) =>
			handler.PlatformView?.UpdateX(view);
 
		public static void MapY(IWindowHandler handler, IWindow view) =>
			handler.PlatformView?.UpdateY(view);
 
		public static void MapWidth(IWindowHandler handler, IWindow view) =>
			handler.PlatformView?.UpdateWidth(view);
 
		public static void MapHeight(IWindowHandler handler, IWindow view) =>
			handler.PlatformView?.UpdateHeight(view);
 
		public static void MapMaximumWidth(IWindowHandler handler, IWindow view) =>
			handler.PlatformView?.UpdateMaximumWidth(view);
 
		public static void MapMaximumHeight(IWindowHandler handler, IWindow view) =>
			handler.PlatformView?.UpdateMaximumHeight(view);
 
		public static void MapMinimumWidth(IWindowHandler handler, IWindow view) =>
			handler.PlatformView?.UpdateMinimumWidth(view);
 
		public static void MapMinimumHeight(IWindowHandler handler, IWindow view) =>
			handler.PlatformView?.UpdateMinimumHeight(view);
 
		public static void MapMenuBar(IWindowHandler handler, IWindow view)
		{
			if (!(OperatingSystem.IsIOSVersionAtLeast(13) || OperatingSystem.IsMacCatalystVersionAtLeast(13)))
				return;
 
			if (MauiUIApplicationDelegate.Current != null &&
				view is IMenuBarElement mb)
			{
				if (MauiUIApplicationDelegate.Current.MenuBuilder == null)
				{
					UIMenuSystem
						.MainSystem
						.SetNeedsRebuild();
				}
				else
				{
					// The handlers that are part of MenuBar
					// are only relevant while the menu is being built
					// because you can only build a menu while the
					// `AppDelegate.BuildMenu` override is running
					mb.MenuBar?.Handler?.DisconnectHandler();
					mb.MenuBar?
						.ToHandler(handler.MauiContext!)?
						.DisconnectHandler();
				}
			}
		}
 
		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<IWindow>? _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);
				}
			}
		}
	}
}