File: Platform\iOS\WindowViewController.cs
Web Access
Project: src\src\Core\src\Core.csproj (Microsoft.Maui)
#if MACCATALYST
using System;
using System.Diagnostics.CodeAnalysis;
using CoreGraphics;
using UIKit;
using System.Threading.Tasks;
 
namespace Microsoft.Maui.Platform;
 
internal class WindowViewController : UIViewController
{
	WeakReference<IView?> _iTitleBarRef;
	bool _isTitleBarVisible = true;
	internal bool IsFirstLayout { get; set; }
	internal bool HasCustomTitleBar { get; set; }
 
   	[UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in device test: 'TitleBar Does Not Leak'")]
	UIView? _titleBar;
 
	[UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in device test: 'TitleBar Does Not Leak'")]
	UIView? _contentWrapperView;
 
	[UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in device test: 'TitleBar Does Not Leak'")]
	internal NSLayoutConstraint? _contentWrapperTopConstraint;
 
	/// <summary>
	/// Instantiate a new <see cref="WindowViewController"/> object.
	/// </summary>
	/// <param name="contentViewController">An instance of the <see cref="UIViewController"/> that is the RootViewController.</param>
	/// <param name="window">An instance of the <see cref="IWindow"/>.</param>
	/// <param name="mauiContext">An instance of the <see cref="IMauiContext"/>.</param>
	/// <remarks>
	/// Only dragging the top of the titlebar will move the window.
	/// The top of the TitleBar will also drag the window inside of elements like buttons.
	/// Gestures such as swiping and controls like swipeview will not work inside the TitleBar.
	/// The TitleBar will not be accessible when a modal is being presented.
	/// </remarks>
	public WindowViewController(UIViewController contentViewController, IWindow window, IMauiContext mauiContext)
	{
		_iTitleBarRef = new WeakReference<IView?>(null);
 
		// Note: Maintain the order for adding a new ViewController to a Container ViewController
		// 1. Add the Subview
		// 2. Arrange the Subview's frame
		// 3. AddChildViewController
		// 4. Call DidMoveToParentViewController
		if (View is not null && contentViewController.View is not null)
		{
			_contentWrapperView = new UIView
			{
				TranslatesAutoresizingMaskIntoConstraints = false
			};
			View.AddSubview(_contentWrapperView);
			_contentWrapperView.AddSubview(contentViewController.View);
			_contentWrapperTopConstraint = _contentWrapperView.TopAnchor.ConstraintEqualTo(View.TopAnchor, 0);
 
			NSLayoutConstraint.ActivateConstraints(new[]
			{
				_contentWrapperView.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor),
				_contentWrapperView.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor),
				_contentWrapperTopConstraint,
				_contentWrapperView.BottomAnchor.ConstraintEqualTo(View.BottomAnchor)
			});
 
		}
 
		SetUpTitleBar(window, mauiContext);
 
		AddChildViewController(contentViewController);
		contentViewController.DidMoveToParentViewController(this);
	}
 
	public override void ViewWillLayoutSubviews()
	{
		LayoutTitleBar();
		base.ViewWillLayoutSubviews();
	}
 
	public override void ViewDidLayoutSubviews()
	{
		UpdateContentWrapperContentFrame();
		base.ViewDidLayoutSubviews();
	}
 
	void UpdateContentWrapperContentFrame()
	{
		// At this point the _contentWrapperView bounds haven't been set
		// so we just use the windows bounds to set this value
		var frame = new CGRect(0, 0, View!.Bounds.Width, View!.Bounds.Height - (_contentWrapperTopConstraint?.Constant ?? 0));
 
		if (_contentWrapperView is not null && _contentWrapperView.Subviews[0].Frame != frame)
		{
			_contentWrapperView.Subviews[0].Frame = frame;
		}
	}
	
	/// <summary>
	/// Sets up the TitleBar in the ViewController.
	/// </summary>
	/// <param name="window">An instance of the <see cref="IWindow"/>.</param>
	/// <param name="mauiContext">An instance of the <see cref="IMauiContext"/>.</param>
	public void SetUpTitleBar(IWindow window, IMauiContext mauiContext)
	{
		var platformWindow = window.Handler?.PlatformView as UIWindow;
 
		if (platformWindow is null || View is null)
		{
			return;
		}
 
		var newTitleBar = window.TitleBar?.ToPlatform(mauiContext);
		HasCustomTitleBar = newTitleBar is not null;
 
		IView? iTitleBar = null;
		_iTitleBarRef?.TryGetTarget(out iTitleBar);
 
		if (newTitleBar != iTitleBar)
		{
			_titleBar?.RemoveFromSuperview();
			iTitleBar?.DisconnectHandlers();
			iTitleBar = null;
 
			if (newTitleBar is not null)
			{
				iTitleBar = window.TitleBar;
				View.AddSubview(newTitleBar);
			}
 
			_titleBar = newTitleBar;
			_iTitleBarRef = new WeakReference<IView?>(iTitleBar);
		}
		
		_isTitleBarVisible = (iTitleBar?.Visibility == Visibility.Visible);
 
		var platformTitleBar = platformWindow.WindowScene?.Titlebar;
 
		if (newTitleBar is not null && platformTitleBar is not null)
		{
			platformTitleBar.TitleVisibility = UITitlebarTitleVisibility.Hidden;
		}
 
		else if (newTitleBar is null && platformTitleBar is not null)
		{
			platformTitleBar.TitleVisibility = UITitlebarTitleVisibility.Visible;
		}
 
		IsFirstLayout = true;
		LayoutTitleBar();
	}
 
	/// <summary>
	/// Measures and arranges the TitleBar and adjusts the frame for the window content to make space for the TitleBar.
	/// </summary>
	public void LayoutTitleBar()
	{
		if (_contentWrapperTopConstraint is null || View is null)
		{
			return;
		}
 
		var current = _contentWrapperTopConstraint.Constant;
		_iTitleBarRef.TryGetTarget(out var iTitleBar);
 
		nfloat titleBarHeight = 0;
 
		if (_isTitleBarVisible && iTitleBar is not null)
		{
			var measured = iTitleBar.Measure(View.Bounds.Width, double.PositiveInfinity);
			iTitleBar.Arrange(new Graphics.Rect(0, 0, View.Bounds.Width, measured.Height));
			titleBarHeight = (nfloat)measured.Height;
		}
 
		_contentWrapperTopConstraint.Constant = titleBarHeight;
 
		UpdateContentWrapperContentFrame();
	}
 
	public void SetTitleBarVisibility(bool isVisible)
	{
		if (_contentWrapperTopConstraint is null || View is null)
			return;
 
		_isTitleBarVisible = isVisible;
		_iTitleBarRef.TryGetTarget(out var iTitleBar);
		iTitleBar?.InvalidateMeasure();
	}
}
#endif // MACCATALYST