File: Compatibility\Handlers\NavigationPage\iOS\NavigationRenderer.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform;
using ObjCRuntime;
using UIKit;
using static Microsoft.Maui.Controls.Compatibility.Platform.iOS.AccessibilityExtensions;
using static Microsoft.Maui.Controls.Compatibility.Platform.iOS.ToolbarItemExtensions;
using static Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.NavigationPage;
using static Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.Page;
using PageUIStatusBarAnimation = Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.UIStatusBarAnimation;
using PointF = CoreGraphics.CGPoint;
using RectangleF = CoreGraphics.CGRect;
using SizeF = CoreGraphics.CGSize;
 
namespace Microsoft.Maui.Controls.Handlers.Compatibility
{
	public class NavigationRenderer : UINavigationController, INavigationViewHandler, IPlatformViewHandler
	{
		internal const string UpdateToolbarButtons = "Xamarin.UpdateToolbarButtons";
		bool _appeared;
		bool _ignorePopCall;
		FlyoutPage _parentFlyoutPage;
		UIViewController[] _removeControllers;
		UIToolbar _secondaryToolbar;
		bool _hasNavigationBar;
		UIImage _defaultNavBarShadowImage;
		UIImage _defaultNavBarBackImage;
		bool _disposed;
		IMauiContext _mauiContext;
		IMauiContext MauiContext => _mauiContext;
		public static IPropertyMapper<NavigationPage, NavigationRenderer> Mapper = new PropertyMapper<NavigationPage, NavigationRenderer>(ViewHandler.ViewMapper)
		{
			[PlatformConfiguration.iOSSpecific.NavigationPage.PrefersLargeTitlesProperty.PropertyName] = NavigationPage.MapPrefersLargeTitles,
			[PlatformConfiguration.iOSSpecific.NavigationPage.IsNavigationBarTranslucentProperty.PropertyName] = NavigationPage.MapIsNavigationBarTranslucent,
		};
 
		public static CommandMapper<NavigationPage, NavigationRenderer> CommandMapper = new CommandMapper<NavigationPage, NavigationRenderer>(ViewHandler.ViewCommandMapper);
		ViewHandlerDelegator<NavigationPage> _viewHandlerWrapper;
		bool _navigating = false;
		WeakReference<VisualElement> _element;
		WeakReference<Page> _current;
		bool _uiRequestedPop; // User tapped the back button or swiped to navigate back
		MauiNavigationDelegate NavigationDelegate => Delegate as MauiNavigationDelegate;
 
		[Internals.Preserve(Conditional = true)]
		public NavigationRenderer() : base(typeof(MauiControlsNavigationBar), null)
		{
			_viewHandlerWrapper = new ViewHandlerDelegator<NavigationPage>(Mapper, CommandMapper, this);
 
			Delegate = new MauiNavigationDelegate(this);
		}
 
		Page Current
		{
			get => _current?.GetTargetOrDefault();
			set => _current = value is null ? null : new(value);
		}
 
		IPageController PageController => Element as IPageController;
 
		NavigationPage NavPage => Element as NavigationPage;
		INavigationPageController NavPageController => NavPage;
 
		public VisualElement Element { get => _viewHandlerWrapper.Element ?? _element?.GetTargetOrDefault(); }
 
		public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
 
#pragma warning disable CS0618 // Type or member is obsolete
		public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
		{
			return VisualElementRenderer<NavigationPage>.GetDesiredSize(this, widthConstraint, heightConstraint,
				new Size(0, 0));
		}
#pragma warning restore CS0618 // Type or member is obsolete
 
		public UIView NativeView
		{
			get { return View; }
		}
 
		public void SetElement(VisualElement element)
		{
			(this as IElementHandler).SetVirtualView(element);
			_element = element is null ? null : new(element);
		}
 
		public UIViewController ViewController
		{
			get { return this; }
		}
 
		//TODO: this was deprecated in iOS8.0 and is not called in 9.0+
		[System.Runtime.Versioning.UnsupportedOSPlatform("ios8.0")]
		[System.Runtime.Versioning.UnsupportedOSPlatform("tvos")]
		public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation)
		{
			base.DidRotate(fromInterfaceOrientation);
 
			View.SetNeedsLayout();
 
			var parentingViewController = GetParentingViewController();
			parentingViewController?.UpdateLeftBarButtonItem();
		}
 
		public Task<bool> PopToRootAsync(Page page, bool animated = true)
		{
			return OnPopToRoot(page, animated);
		}
 
		public override UIViewController[] PopToRootViewController(bool animated)
		{
			if (!_ignorePopCall && ViewControllers.Length > 1)
				RemoveViewControllers(animated);
 
			return base.PopToRootViewController(animated);
		}
 
		public Task<bool> PopViewAsync(Page page, bool animated = true)
		{
			return OnPopViewAsync(page, animated);
		}
 
		public override UIViewController PopViewController(bool animated)
		{
			RemoveViewControllers(animated);
			return base.PopViewController(animated);
		}
 
		public Task<bool> PushPageAsync(Page page, bool animated = true)
		{
			return OnPushAsync(page, animated);
		}
 
		public override void ViewDidAppear(bool animated)
		{
			if (!_appeared)
			{
				_appeared = true;
				PageController?.SendAppearing();
			}
 
			base.ViewDidAppear(animated);
 
			View.SetNeedsLayout();
		}
 
		public override void ViewWillAppear(bool animated)
		{
			base.ViewWillAppear(animated);
 
			SetStatusBarStyle();
		}
 
		public override void ViewDidDisappear(bool animated)
		{
			CompletePendingNavigation(false);
 
			base.ViewDidDisappear(animated);
 
			if (!_appeared || Element == null)
				return;
 
			_appeared = false;
			PageController.SendDisappearing();
		}
 
		public override void ViewWillLayoutSubviews()
		{
			base.ViewWillLayoutSubviews();
 
			if (Current is not Page current)
				return;
 
			UpdateToolBarVisible();
 
			var navBarFrameBottom = Math.Min(NavigationBar.Frame.Bottom, 140);
			var toolbar = _secondaryToolbar;
 
			//save the state of the Current page we are calculating, this will fire before Current is updated
			_hasNavigationBar = NavigationPage.GetHasNavigationBar(current);
 
			// Use 0 if the NavBar is hidden or will be hidden
			var toolbarY = NavigationBarHidden || NavigationBar.Translucent || !_hasNavigationBar ? 0 : navBarFrameBottom;
			toolbar.Frame = new RectangleF(0, toolbarY, View.Frame.Width, toolbar.Frame.Height);
 
			// TODO .NET 10
			// This is required in order to set the "Frame" property on NavigationPage
			// It'd be good to see if we can optimize this a bit better.
			(Element as IView).Arrange(View.Bounds.ToRectangle());
		}
 
		public override void ViewDidLoad()
		{
			base.ViewDidLoad();
 
			UpdateTranslucent();
 
			_secondaryToolbar = new SecondaryToolbar { Frame = new RectangleF(0, 0, 320, 44) };
			View.Add(_secondaryToolbar);
			_secondaryToolbar.Hidden = true;
 
			FindParentFlyoutPage();
 
			var navPage = NavPage;
			INavigationPageController navPageController = NavPage;
			if (navPage.CurrentPage == null)
			{
				throw new InvalidOperationException(
					"NavigationPage must have a root Page before being used. Either call PushAsync with a valid Page, or pass a Page to the constructor before usage.");
			}
 
			navPageController.PushRequested += OnPushRequested;
			navPageController.PopRequested += OnPopRequested;
			navPageController.PopToRootRequested += OnPopToRootRequested;
			navPageController.RemovePageRequested += OnRemovedPageRequested;
			navPageController.InsertPageBeforeRequested += OnInsertPageBeforeRequested;
 
			UpdateBarBackground();
			UpdateBarTextColor();
			UpdateHideNavigationBarSeparator();
			UpdateUseLargeTitles();
 
			if (OperatingSystem.IsIOSVersionAtLeast(11))
				SetNeedsUpdateOfHomeIndicatorAutoHidden();
 
			// If there is already stuff on the stack we need to push it
			NavPageController.Pages.ForEach(async p => await PushPageAsync(p, false));
 
			Element.PropertyChanged += HandlePropertyChanged;
 
			UpdateToolBarVisible();
			UpdateBackgroundColor();
			Current = navPage.CurrentPage;
		}
 
		protected override void Dispose(bool disposing)
		{
			if (_disposed)
			{
				return;
			}
 
			_disposed = true;
 
			if (disposing)
			{
				Delegate = null;
				foreach (var childViewController in ViewControllers)
					childViewController.Dispose();
 
				_secondaryToolbar.RemoveFromSuperview();
				_secondaryToolbar.Dispose();
				_secondaryToolbar = null;
 
				_parentFlyoutPage = null;
				Current = null; // unhooks events
 
				var navPage = NavPage;
				INavigationPageController navPageController = NavPage;
				navPage.PropertyChanged -= HandlePropertyChanged;
 
				navPageController.PushRequested -= OnPushRequested;
				navPageController.PopRequested -= OnPopRequested;
				navPageController.PopToRootRequested -= OnPopToRootRequested;
				navPageController.RemovePageRequested -= OnRemovedPageRequested;
				navPageController.InsertPageBeforeRequested -= OnInsertPageBeforeRequested;
			}
 
			base.Dispose(disposing);
 
			if (disposing && _appeared)
			{
				PageController.SendDisappearing();
 
				_appeared = false;
			}
		}
 
		protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
		{
			ElementChanged?.Invoke(this, e);
		}
 
		protected virtual async Task<bool> OnPopToRoot(Page page, bool animated)
		{
			_ignorePopCall = true;
			_ = page.ToPlatform(MauiContext);
			var renderer = (IPlatformViewHandler)page.Handler;
			if (renderer == null || renderer.ViewController == null)
				return false;
 
			var task = GetAppearedOrDisappearedTask(page);
 
			PopToRootViewController(animated);
 
			_ignorePopCall = false;
			var success = !await task;
 
			UpdateToolBarVisible();
			return success;
		}
 
		protected virtual async Task<bool> OnPopViewAsync(Page page, bool animated)
		{
			if (_ignorePopCall)
				return true;
 
			_ = page.ToPlatform(MauiContext);
			var renderer = (IPlatformViewHandler)page.Handler;
			if (renderer == null || renderer.ViewController == null)
				return false;
 
			if (page != ((ParentingViewController)TopViewController).Child)
				throw new NotSupportedException("Popped page does not appear on top of current navigation stack, please file a bug.");
 
			var task = GetAppearedOrDisappearedTask(page);
 
			UIViewController poppedViewController;
			_ignorePopCall = true;
			poppedViewController = base.PopViewController(animated);
 
			var actuallyRemoved = poppedViewController == null ? true : !await task;
			_ignorePopCall = false;
 
			if (poppedViewController is ParentingViewController pvc)
				pvc.Disconnect(false);
			else
				poppedViewController?.Dispose();
 
			UpdateToolBarVisible();
			return actuallyRemoved;
		}
 
		protected virtual async Task<bool> OnPushAsync(Page page, bool animated)
		{
			if (page is FlyoutPage)
				System.Diagnostics.Trace.WriteLine($"Pushing a {nameof(FlyoutPage)} onto a {nameof(NavigationPage)} is not a supported UI pattern on iOS. " +
					"Please see https://developer.apple.com/documentation/uikit/uisplitviewcontroller for more details.");
 
			var pack = CreateViewControllerForPage(page);
			var task = GetAppearedOrDisappearedTask(page);
 
			PushViewController(pack, animated);
 
			var shown = await task;
			UpdateToolBarVisible();
			return shown;
		}
 
		public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
		{
#pragma warning disable CA1422 // Validate platform compatibility
			base.TraitCollectionDidChange(previousTraitCollection);
#pragma warning restore CA1422 // Validate platform compatibility
			// Make sure the control adheres to changes in UI theme
			if (OperatingSystem.IsIOSVersionAtLeast(13) && previousTraitCollection?.UserInterfaceStyle != TraitCollection.UserInterfaceStyle)
				UpdateBackgroundColor();
		}
 
 
		ParentingViewController CreateViewControllerForPage(Page page)
		{
			_ = page.ToPlatform(MauiContext);
 
			// must pack into container so padding can work
			// otherwise the view controller is forced to 0,0
			var pack = new ParentingViewController(this) { Child = page };
 
			pack.UpdateTitleArea(page);
 
			var pageRenderer = (IPlatformViewHandler)page.Handler;
			pack.View.AddSubview(pageRenderer.ViewController.View);
			pack.AddChildViewController(pageRenderer.ViewController);
			pageRenderer.ViewController.DidMoveToParentViewController(pack);
 
			return pack;
		}
 
		ParentingViewController GetParentingViewController()
		{
			if (!ViewControllers.Any())
				return null;
 
			return ViewControllers.Last() as ParentingViewController;
		}
 
		void FindParentFlyoutPage()
		{
			var page = Element as Page;
			var parentPages = page.GetParentPages();
			var flyoutDetail = parentPages.OfType<FlyoutPage>().FirstOrDefault();
 
			if (flyoutDetail != null && parentPages.Append((Page)Element).Contains(flyoutDetail.Detail))
				_parentFlyoutPage = flyoutDetail;
		}
 
		TaskCompletionSource<bool> _pendingNavigationRequest;
		ActionDisposable _removeLifecycleEvents;
 
		void CompletePendingNavigation(bool success)
		{
			if (_pendingNavigationRequest is null)
				return;
 
			_removeLifecycleEvents?.Dispose();
			_removeLifecycleEvents = null;
 
			var pendingNavigationRequest = _pendingNavigationRequest;
			_pendingNavigationRequest = null;
 
			BeginInvokeOnMainThread(() =>
			{
				pendingNavigationRequest?.TrySetResult(success);
				pendingNavigationRequest = null;
			});
		}
 
		Task<bool> GetAppearedOrDisappearedTask(Page page)
		{
			CompletePendingNavigation(false);
 
			_pendingNavigationRequest = new TaskCompletionSource<bool>();
 
			_ = page.ToPlatform(MauiContext);
			var renderer = (IPlatformViewHandler)page.Handler;
			var parentViewController = renderer.ViewController.ParentViewController as ParentingViewController;
			if (parentViewController == null)
				throw new NotSupportedException("ParentingViewController parent could not be found. Please file a bug.");
 
			EventHandler appearing = null, disappearing = null;
			appearing = (s, e) =>
			{
				CompletePendingNavigation(true);
			};
 
			disappearing = (s, e) =>
			{
				CompletePendingNavigation(false);
			};
 
			if (NavigationDelegate is not null)
				NavigationDelegate.WaitingForNavigationToFinish = true;
 
			_removeLifecycleEvents = new ActionDisposable(() =>
			{
				// This ensures that we don't cause multiple calls to CompletePendingNavigation.
				// Depending on circumstances (covered by modal page) CompletePendingNavigation 
				// might get called from the Delegate vs the DidAppear/DidDisappear methods
				// on the ParentingViewController.
				parentViewController.Appearing -= appearing;
				parentViewController.Disappearing -= disappearing;
				if (NavigationDelegate is not null)
					NavigationDelegate.WaitingForNavigationToFinish = false;
			});
 
			parentViewController.Appearing += appearing;
			parentViewController.Disappearing += disappearing;
 
			return _pendingNavigationRequest.Task;
		}
 
		void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == NavigationPage.BarBackgroundColorProperty.PropertyName ||
				e.PropertyName == NavigationPage.BarBackgroundProperty.PropertyName)
			{
				UpdateBarBackground();
			}
			else if (e.PropertyName == NavigationPage.BarTextColorProperty.PropertyName
				  || e.PropertyName == StatusBarTextColorModeProperty.PropertyName)
			{
				UpdateBarTextColor();
				SetStatusBarStyle();
			}
			else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
			{
				UpdateBackgroundColor();
			}
			else if (e.PropertyName == NavigationPage.CurrentPageProperty.PropertyName)
			{
				var current = Current = NavPage?.CurrentPage;
				ValidateNavbarExists(current);
			}
			else if (e.PropertyName == IsNavigationBarTranslucentProperty.PropertyName)
			{
				UpdateTranslucent();
			}
			else if (e.PropertyName == PreferredStatusBarUpdateAnimationProperty.PropertyName)
			{
				UpdateCurrentPagePreferredStatusBarUpdateAnimation();
			}
			else if (e.PropertyName == PrefersLargeTitlesProperty.PropertyName)
			{
				UpdateUseLargeTitles();
			}
			else if (e.PropertyName == NavigationPage.BackButtonTitleProperty.PropertyName || e.PropertyName == NavigationPage.TitleProperty.PropertyName)
			{
				var pack = (ParentingViewController)TopViewController;
				pack?.UpdateTitleArea(pack.Child);
			}
			else if (e.PropertyName == HideNavigationBarSeparatorProperty.PropertyName)
			{
				UpdateHideNavigationBarSeparator();
			}
			else if (e.PropertyName == PrefersHomeIndicatorAutoHiddenProperty.PropertyName)
			{
				UpdateHomeIndicatorAutoHidden();
			}
			else if (e.PropertyName == PrefersStatusBarHiddenProperty.PropertyName)
			{
				UpdateStatusBarHidden();
			}
		}
 
		void ValidateNavbarExists(Page newCurrentPage)
		{
			//if the last time we did ViewDidLayoutSubviews we had other value for _hasNavigationBar
			//we will need to relayout. This is because Current is updated async of the layout happening
			if (_hasNavigationBar != NavigationPage.GetHasNavigationBar(newCurrentPage))
				View.InvalidateMeasure(Element);
		}
 
		void UpdateHomeIndicatorAutoHidden()
		{
			if (Element == null)
				return;
 
			SetNeedsUpdateOfHomeIndicatorAutoHidden();
		}
 
		void UpdateStatusBarHidden()
		{
			if (Element == null)
				return;
 
			SetNeedsStatusBarAppearanceUpdate();
		}
 
		void UpdateHideNavigationBarSeparator()
		{
			bool shouldHide = NavPage.OnThisPlatform().HideNavigationBarSeparator();
 
			// Just setting the ShadowImage is good for iOS11
			if (_defaultNavBarShadowImage == null)
				_defaultNavBarShadowImage = NavigationBar.ShadowImage;
 
			if (OperatingSystem.IsIOSVersionAtLeast(13) || OperatingSystem.IsMacCatalystVersionAtLeast(13))
			{
				if (shouldHide)
				{
					NavigationBar.CompactAppearance.ShadowColor = UIColor.Clear;
					NavigationBar.StandardAppearance.ShadowColor = UIColor.Clear;
					NavigationBar.ScrollEdgeAppearance.ShadowColor = UIColor.Clear;
				}
				else
				{
					NavigationBar.CompactAppearance.ShadowColor = UIColor.FromRGBA(0, 0, 0, 76); //default ios13 shadow color
					NavigationBar.StandardAppearance.ShadowColor = UIColor.FromRGBA(0, 0, 0, 76);
					NavigationBar.ScrollEdgeAppearance.ShadowColor = UIColor.FromRGBA(0, 0, 0, 76);
				}
			}
			else
			{
				if (shouldHide)
					NavigationBar.ShadowImage = new UIImage();
				else
					NavigationBar.ShadowImage = _defaultNavBarShadowImage;
			}
 
			if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsMacCatalystVersionAtLeast(11)))
			{
				// For iOS 10 and lower, you need to set the background image.
				// If you set this for iOS11, you'll remove the background color.
				if (_defaultNavBarBackImage == null)
					_defaultNavBarBackImage = NavigationBar.GetBackgroundImage(UIBarMetrics.Default);
 
				if (shouldHide)
					NavigationBar.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
				else
					NavigationBar.SetBackgroundImage(_defaultNavBarBackImage, UIBarMetrics.Default);
			}
		}
 
		void UpdateCurrentPagePreferredStatusBarUpdateAnimation()
		{
			// Not using the extension method syntax here because for some reason it confuses the mono compiler
			// and throws a CS0121 error
			PageUIStatusBarAnimation animation = PlatformConfiguration.iOSSpecific.Page.PreferredStatusBarUpdateAnimation(((Page)Element).OnThisPlatform());
			PlatformConfiguration.iOSSpecific.Page.SetPreferredStatusBarUpdateAnimation(Current.OnThisPlatform(), animation);
		}
 
		void UpdateUseLargeTitles()
		{
			_viewHandlerWrapper.UpdateProperty(PrefersLargeTitlesProperty.PropertyName);
		}
 
		void UpdateTranslucent()
		{
			_viewHandlerWrapper.UpdateProperty(IsNavigationBarTranslucentProperty.PropertyName);
		}
 
		void InsertPageBefore(Page page, Page before)
		{
			if (before.Handler is not IPlatformViewHandler nvh)
				throw new ArgumentNullException(nameof(before));
			if (page == null)
				throw new ArgumentNullException(nameof(page));
 
			var pageContainer = CreateViewControllerForPage(page);
			var target = nvh.ViewController.ParentViewController;
			ViewControllers = ViewControllers.Insert(ViewControllers.IndexOf(target), pageContainer);
		}
 
		void OnInsertPageBeforeRequested(object sender, NavigationRequestedEventArgs e)
		{
			InsertPageBefore(e.Page, e.BeforePage);
		}
 
		void OnPopRequested(object sender, NavigationRequestedEventArgs e)
		{
			e.Task = PopViewAsync(e.Page, e.Animated);
		}
 
		void OnPopToRootRequested(object sender, NavigationRequestedEventArgs e)
		{
			e.Task = PopToRootAsync(e.Page, e.Animated);
		}
 
		void OnPushRequested(object sender, NavigationRequestedEventArgs e)
		{
			// If any text entry controls have focus, we need to end their editing session
			// so that they are not the first responder; if we don't some things (like the activity indicator
			// on pull-to-refresh) will not work correctly.
			View?.Window?.EndEditing(true);
 
			e.Task = PushPageAsync(e.Page, e.Animated);
		}
 
		void OnRemovedPageRequested(object sender, NavigationRequestedEventArgs e)
		{
			RemovePage(e.Page);
		}
 
		void RemovePage(Page page)
		{
			if (page?.Handler is not IPlatformViewHandler nvh)
				throw new ArgumentNullException(nameof(page));
			if (page == Current)
				throw new NotSupportedException(); // should never happen as NavPage protects against this
 
			var target = nvh.ViewController.ParentViewController;
 
			// So the ViewControllers property is not very property like on iOS. Assigning to it doesn't cause it to be
			// immediately reflected into the property. The change will not be reflected until there has been sufficient time
			// to process it (it ends up on the event queue). So to resolve this issue we keep our own stack until we
			// know iOS has processed it, and make sure any updates use that.
 
			// In the future we may want to make RemovePageAsync and deprecate RemovePage to handle cases where Push/Pop is called
			// during a remove cycle.
 
			if (_removeControllers == null)
			{
				_removeControllers = ViewControllers.Remove(target);
				ViewControllers = _removeControllers;
				BeginInvokeOnMainThread(() => { _removeControllers = null; });
			}
			else
			{
				_removeControllers = _removeControllers.Remove(target);
				ViewControllers = _removeControllers;
			}
			target.Dispose();
			var parentingViewController = GetParentingViewController();
			parentingViewController?.UpdateLeftBarButtonItem(page);
		}
 
		void RemoveViewControllers(bool animated)
		{
			var controller = TopViewController as ParentingViewController;
			if (controller?.Child is not Page child || child.Handler == null)
				return;
 
			// Gesture in progress, lets not be proactive and just wait for it to finish
			var task = GetAppearedOrDisappearedTask(child);
 
			task.ContinueWith(t =>
			{
				// task returns true if the user lets go of the page and is not popped
				// however at this point the renderer is already off the visual stack so we just need to update the NavigationPage
				// Also worth noting this task returns on the main thread
				if (t.Result)
					return;
				// because we skip the normal pop process we need to dispose ourselves
				controller?.Dispose();
			}, TaskScheduler.FromCurrentSynchronizationContext());
		}
 
		void UpdateBackgroundColor()
		{
			var color = Element.BackgroundColor == null ? Maui.Platform.ColorExtensions.BackgroundColor : Element.BackgroundColor.ToPlatform();
			View.BackgroundColor = color;
		}
 
		void UpdateBarBackground()
		{
			var barBackgroundColor = NavPage.BarBackgroundColor;
			var barBackgroundBrush = NavPage.BarBackground;
 
			// if the brush has a solid color, treat it as a Color so we can compute the alpha value
			if (NavPage.BarBackground is SolidColorBrush scb)
			{
				barBackgroundColor = scb.Color;
				barBackgroundBrush = null;
			}
 
			if (OperatingSystem.IsIOSVersionAtLeast(13) || OperatingSystem.IsMacCatalystVersionAtLeast(13))
			{
				var navigationBarAppearance = NavigationBar.StandardAppearance;
				if (barBackgroundColor is null)
				{
					navigationBarAppearance.ConfigureWithOpaqueBackground();
					navigationBarAppearance.BackgroundColor = Maui.Platform.ColorExtensions.BackgroundColor;
 
					var parentingViewController = GetParentingViewController();
					parentingViewController?.SetupDefaultNavigationBarAppearance();
				}
				else
				{
					if (barBackgroundColor?.Alpha < 1f)
						navigationBarAppearance.ConfigureWithTransparentBackground();
					else
						navigationBarAppearance.ConfigureWithOpaqueBackground();
 
					navigationBarAppearance.BackgroundColor = barBackgroundColor.ToPlatform();
				}
 
				if (barBackgroundBrush is not null)
				{
					var backgroundImage = NavigationBar.GetBackgroundImage(barBackgroundBrush);
 
					navigationBarAppearance.BackgroundImage = backgroundImage;
				}
 
				NavigationBar.CompactAppearance = navigationBarAppearance;
				NavigationBar.StandardAppearance = navigationBarAppearance;
				NavigationBar.ScrollEdgeAppearance = navigationBarAppearance;
			}
			else
			{
				if (barBackgroundColor?.Alpha == 0f)
				{
					NavigationBar.SetTransparentNavigationBar();
				}
				else
				{
					// Set navigation bar background color
					NavigationBar.BarTintColor = barBackgroundColor == null
						? UINavigationBar.Appearance.BarTintColor
						: barBackgroundColor.ToPlatform();
 
					var backgroundImage = NavigationBar.GetBackgroundImage(barBackgroundBrush);
					NavigationBar.SetBackgroundImage(backgroundImage, UIBarMetrics.Default);
				}
			}
		}
 
		void UpdateBarTextColor()
		{
			var barTextColor = NavPage.BarTextColor;
 
			// Determine new title text attributes via global static data
			var globalTitleTextAttributes = UINavigationBar.Appearance.TitleTextAttributes;
			var titleTextAttributes = new UIStringAttributes
			{
				ForegroundColor = barTextColor == null ? globalTitleTextAttributes?.ForegroundColor : barTextColor.ToPlatform(),
				Font = globalTitleTextAttributes?.Font
			};
 
			// Determine new large title text attributes via global static data
			var largeTitleTextAttributes = titleTextAttributes;
			if (OperatingSystem.IsIOSVersionAtLeast(11))
			{
				var globalLargeTitleTextAttributes = UINavigationBar.Appearance.LargeTitleTextAttributes;
 
				largeTitleTextAttributes = new UIStringAttributes
				{
					ForegroundColor = barTextColor == null ? globalLargeTitleTextAttributes?.ForegroundColor : barTextColor.ToPlatform(),
					Font = globalLargeTitleTextAttributes?.Font
				};
			}
 
			if (OperatingSystem.IsIOSVersionAtLeast(13))
			{
				NavigationBar.CompactAppearance.TitleTextAttributes = titleTextAttributes;
				NavigationBar.CompactAppearance.LargeTitleTextAttributes = largeTitleTextAttributes;
 
				NavigationBar.StandardAppearance.TitleTextAttributes = titleTextAttributes;
				NavigationBar.StandardAppearance.LargeTitleTextAttributes = largeTitleTextAttributes;
 
				NavigationBar.ScrollEdgeAppearance.TitleTextAttributes = titleTextAttributes;
				NavigationBar.ScrollEdgeAppearance.LargeTitleTextAttributes = largeTitleTextAttributes;
			}
			else
			{
				NavigationBar.TitleTextAttributes = titleTextAttributes;
 
				if (OperatingSystem.IsIOSVersionAtLeast(11))
					NavigationBar.LargeTitleTextAttributes = largeTitleTextAttributes;
			}
 
			// set Tint color (i. e. Back Button arrow and Text)
			var iconColor = Current is Page current ? NavigationPage.GetIconColor(current) : null;
			if (iconColor == null)
				iconColor = barTextColor;
 
			NavigationBar.TintColor = iconColor == null || NavPage.OnThisPlatform().GetStatusBarTextColorMode() == StatusBarTextColorMode.DoNotAdjust
				? UINavigationBar.Appearance.TintColor
				: iconColor.ToPlatform();
		}
 
		void SetStatusBarStyle()
		{
			var barTextColor = NavPage.BarTextColor;
			var statusBarColorMode = NavPage.OnThisPlatform().GetStatusBarTextColorMode();
 
#pragma warning disable CA1416, CA1422 // TODO:   'UIApplication.StatusBarStyle' is unsupported on: 'ios' 9.0 and later
			if (statusBarColorMode == StatusBarTextColorMode.DoNotAdjust || barTextColor?.GetLuminosity() <= 0.5)
			{
				// Use dark text color for status bar
				if (OperatingSystem.IsIOSVersionAtLeast(13) || OperatingSystem.IsMacCatalystVersionAtLeast(13))
				{
					UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.DarkContent;
				}
				else
				{
					UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.Default;
				}
			}
			else
			{
				// Use light text color for status bar
				UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent;
			}
#pragma warning restore CA1416, CA1422
		}
 
		void UpdateToolBarVisible()
		{
			if (_secondaryToolbar == null)
				return;
 
			bool currentHidden = _secondaryToolbar.Hidden;
			if (TopViewController != null && TopViewController.ToolbarItems != null && TopViewController.ToolbarItems.Any())
			{
				_secondaryToolbar.Hidden = false;
				_secondaryToolbar.Items = TopViewController.ToolbarItems;
			}
			else
			{
				_secondaryToolbar.Hidden = true;
				//secondaryToolbar.Items = null;
			}
 
			if (currentHidden != _secondaryToolbar.Hidden)
			{
				if (Current is Page current && current.Handler is not null)
					current.ToPlatform().InvalidateMeasure(current);
 
				if (VisibleViewController is ParentingViewController pvc)
					pvc.UpdateFrames();
			}
 
			TopViewController?.NavigationItem?.TitleView?.SizeToFit();
			TopViewController?.NavigationItem?.TitleView?.LayoutSubviews();
		}
 
		async Task UpdateFormsInnerNavigation(Page pageBeingRemoved)
		{
			if (NavPage == null)
				return;
			if (_ignorePopCall)
				return;
 
			_ignorePopCall = true;
			if (Element.Navigation.NavigationStack.Contains(pageBeingRemoved))
			{
				await (NavPage as INavigationPageController)?.RemoveAsyncInner(pageBeingRemoved, false, true);
				if (_uiRequestedPop)
				{
					NavPage?.SendNavigatedFromHandler(pageBeingRemoved, NavigationType.Pop);
				}
			}
 
			_ignorePopCall = false;
			_uiRequestedPop = false;
		}
 
		[Export("navigationBar:shouldPopItem:")]
		[Internals.Preserve(Conditional = true)]
		internal bool ShouldPopItem(UINavigationBar _, UINavigationItem __)
		{
			_uiRequestedPop = true;
			return true;
		}
 
		[Export("navigationBar:didPopItem:")]
		[Internals.Preserve(Conditional = true)]
		bool DidPopItem(UINavigationBar _, UINavigationItem __)
		{
			_uiRequestedPop = true;
			return true;
		}
 
		internal static void SetFlyoutLeftBarButton(UIViewController containerController, FlyoutPage FlyoutPage)
		{
			if (!FlyoutPage.ShouldShowToolbarButton())
			{
				containerController.NavigationItem.LeftBarButtonItem = null;
				return;
			}
 
 
			FlyoutPage.Flyout.IconImageSource.LoadImage(FlyoutPage.FindMauiContext(), result =>
			{
				var icon = result?.Value;
				if (icon != null)
				{
					try
					{
						containerController.NavigationItem.LeftBarButtonItem = new UIBarButtonItem(icon, UIBarButtonItemStyle.Plain, OnItemTapped);
					}
					catch (Exception)
					{
						// Throws Exception otherwise would catch more specific exception type
					}
				}
 
				if (icon == null || containerController.NavigationItem.LeftBarButtonItem == null)
				{
					containerController.NavigationItem.LeftBarButtonItem = new UIBarButtonItem(FlyoutPage.Flyout.Title, UIBarButtonItemStyle.Plain, OnItemTapped);
				}
 
				if (FlyoutPage != null && !string.IsNullOrEmpty(FlyoutPage.AutomationId))
					SetAutomationId(containerController.NavigationItem.LeftBarButtonItem, $"btn_{FlyoutPage.AutomationId}");
 
				containerController.NavigationItem.LeftBarButtonItem.SetAccessibilityHint(FlyoutPage);
				containerController.NavigationItem.LeftBarButtonItem.SetAccessibilityLabel(FlyoutPage);
			});
 
			void OnItemTapped(object sender, EventArgs e)
			{
				FlyoutPage.IsPresented = !FlyoutPage.IsPresented;
			}
		}
 
		static void SetAccessibilityHint(UIBarButtonItem uIBarButtonItem, Element element)
		{
			if (element == null)
				return;
 
			if (_defaultAccessibilityHint == null)
				_defaultAccessibilityHint = uIBarButtonItem.AccessibilityHint;
 
#pragma warning disable CS0618 // Type or member is obsolete
			uIBarButtonItem.AccessibilityHint = (string)element.GetValue(AutomationProperties.HelpTextProperty) ?? _defaultAccessibilityHint;
#pragma warning restore CS0618 // Type or member is obsolete
		}
 
		static void SetAccessibilityLabel(UIBarButtonItem uIBarButtonItem, Element element)
		{
			if (element == null)
				return;
 
			if (_defaultAccessibilityLabel == null)
				_defaultAccessibilityLabel = uIBarButtonItem.AccessibilityLabel;
 
#pragma warning disable CS0618 // Type or member is obsolete
			uIBarButtonItem.AccessibilityLabel = (string)element.GetValue(AutomationProperties.NameProperty) ?? _defaultAccessibilityLabel;
#pragma warning restore CS0618 // Type or member is obsolete
		}
 
		static void SetIsAccessibilityElement(UIBarButtonItem uIBarButtonItem, Element element)
		{
			if (element == null)
				return;
 
			if (!_defaultIsAccessibilityElement.HasValue)
				_defaultIsAccessibilityElement = uIBarButtonItem.IsAccessibilityElement;
 
			uIBarButtonItem.IsAccessibilityElement = (bool)((bool?)element.GetValue(AutomationProperties.IsInAccessibleTreeProperty) ?? _defaultIsAccessibilityElement);
		}
 
		static void SetAccessibilityElementsHidden(UIBarButtonItem uIBarButtonItem, Element element)
		{
			if (element == null)
				return;
 
			if (!_defaultAccessibilityElementsHidden.HasValue)
				_defaultAccessibilityElementsHidden = uIBarButtonItem.AccessibilityElementsHidden;
 
			uIBarButtonItem.AccessibilityElementsHidden = (bool)((bool?)element.GetValue(AutomationProperties.ExcludedWithChildrenProperty) ?? _defaultAccessibilityElementsHidden);
		}
 
		static void SetAutomationId(UIBarButtonItem uIBarButtonItem, string id)
		{
			uIBarButtonItem.AccessibilityIdentifier = id;
		}
 
		static string _defaultAccessibilityLabel;
		static string _defaultAccessibilityHint;
		static bool? _defaultIsAccessibilityElement;
		static bool? _defaultAccessibilityElementsHidden;
 
		class SecondaryToolbar : UIToolbar
		{
			readonly List<UIView> _lines = new List<UIView>();
 
			public SecondaryToolbar()
			{
				TintColor = UIColor.White;
			}
 
			public override UIBarButtonItem[] Items
			{
				get { return base.Items; }
				set
				{
					base.Items = value;
					SetupLines();
				}
			}
 
			public override void LayoutSubviews()
			{
				base.LayoutSubviews();
				if (Items == null || Items.Length == 0)
					return;
				LayoutToolbarItems(Bounds.Width, Bounds.Height, 0);
			}
 
			void LayoutToolbarItems(nfloat toolbarWidth, nfloat toolbarHeight, nfloat padding)
			{
				var x = padding;
				var y = 0;
				var itemH = toolbarHeight;
				var itemW = toolbarWidth / Items.Length;
 
				foreach (var item in Items)
				{
					var frame = new RectangleF(x, y, itemW, itemH);
					if (frame == item.CustomView.Frame)
						continue;
					item.CustomView.Frame = frame;
					x += itemW + padding;
				}
 
				x = itemW + padding * 1.5f;
				y = (int)Bounds.GetMidY();
				foreach (var l in _lines)
				{
					l.Center = new PointF(x, y);
					x += itemW + padding;
				}
			}
 
			void SetupLines()
			{
				_lines.ForEach(l => l.RemoveFromSuperview());
				_lines.Clear();
				if (Items == null)
					return;
				for (var i = 1; i < Items.Length; i++)
				{
					var l = new UIView(new RectangleF(0, 0, 1, 24)) { BackgroundColor = new UIColor(0, 0, 0, 0.2f) };
					AddSubview(l);
					_lines.Add(l);
				}
			}
		}
 
		class MauiNavigationDelegate : UINavigationControllerDelegate
		{
			bool _finishedWithInitialNavigation;
			readonly WeakReference<NavigationRenderer> _navigation;
 
			public bool WaitingForNavigationToFinish { get; internal set; }
 
			public MauiNavigationDelegate(NavigationRenderer navigationRenderer)
			{
				_navigation = new WeakReference<NavigationRenderer>(navigationRenderer);
			}
 
			public override void DidShowViewController(UINavigationController navigationController, [Transient] UIViewController viewController, bool animated)
			{
				if (_navigation.TryGetTarget(out NavigationRenderer r))
				{
					r._navigating = false;
					if (r.VisibleViewController is ParentingViewController pvc)
					{
						pvc.UpdateFrames();
					}
 
					if (r.Element is NavigationPage np && !_finishedWithInitialNavigation)
					{
						_finishedWithInitialNavigation = true;
						np.SendNavigatedFromHandler(null, NavigationType.Push);
					}
 
					if (WaitingForNavigationToFinish)
						r.CompletePendingNavigation(true);
				}
			}
 
			public override void WillShowViewController(UINavigationController navigationController, [Transient] UIViewController viewController, bool animated)
			{
				if (_navigation.TryGetTarget(out NavigationRenderer r))
				{
					r._navigating = true;
				}
			}
		}
 
		class ParentingViewController : UIViewController
		{
			readonly WeakReference<NavigationRenderer> _navigation;
 
			WeakReference<Page> _child;
			bool _disposed;
			ToolbarTracker _tracker = new ToolbarTracker();
 
			public ParentingViewController(NavigationRenderer navigation)
			{
#pragma warning disable CA1416, CA1422 // TODO: 'UIViewController.AutomaticallyAdjustsScrollViewInsets' is unsupported on: 'ios' 11.0 and later
				AutomaticallyAdjustsScrollViewInsets = false;
#pragma warning restore CA1416, CA1422
 
				_navigation = new WeakReference<NavigationRenderer>(navigation);
			}
 
			public Page Child
			{
				get => _child?.GetTargetOrDefault();
				set
				{
					var child = Child;
					if (child == value)
						return;
 
					if (child is not null)
					{
						child.PropertyChanged -= HandleChildPropertyChanged;
					}
 
					if (value is not null)
					{
						_child = new(value);
						value.PropertyChanged += HandleChildPropertyChanged;
					}
					else
					{
						_child = null;
					}
 
					UpdateHasBackButton();
					UpdateLargeTitles();
					UpdateIconColor();
				}
			}
 
			public event EventHandler Appearing;
 
			[System.Runtime.Versioning.UnsupportedOSPlatform("ios8.0")]
			[System.Runtime.Versioning.UnsupportedOSPlatform("tvos")]
			public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation)
			{
				base.DidRotate(fromInterfaceOrientation);
 
				View.SetNeedsLayout();
			}
 
			public event EventHandler Disappearing;
 
			public override void ViewDidAppear(bool animated)
			{
				base.ViewDidAppear(animated);
 
				Appearing?.Invoke(this, EventArgs.Empty);
			}
 
			public override void ViewDidDisappear(bool animated)
			{
				base.ViewDidDisappear(animated);
 
				// force a redraw for right toolbar items by resetting TintColor to prevent
				// toolbar items being grayed out when canceling swipe to a previous page
				foreach (var item in NavigationItem?.RightBarButtonItems)
				{
					if (item.Image != null)
						continue;
 
					var tintColor = item.TintColor;
					item.TintColor = tintColor == null ? UIColor.Clear : null;
					item.TintColor = tintColor;
				}
 
				Disappearing?.Invoke(this, EventArgs.Empty);
			}
 
			public void UpdateFrames()
			{
				NavigationRenderer n;
 
				// We only want to update the frame after the navigation has settled
				// The frame bounces a bit during navigation and it messes up 
				// the animation changing the frame during navigation
				if (_navigation.TryGetTarget(out n) &&
					ChildViewControllers.Length > 0 &&
					!n._disposed &&
					!n._navigating
					)
				{
					var vc = ChildViewControllers[^1];
 
					if (vc is null)
						return;
 
					var newAdditionalSafeArea = vc.AdditionalSafeAreaInsets;
					var offset = n._secondaryToolbar.Hidden ? 0 : n._secondaryToolbar.Frame.Height;
 
					if (newAdditionalSafeArea.Top != offset)
					{
						newAdditionalSafeArea.Top = offset;
						vc.AdditionalSafeAreaInsets = newAdditionalSafeArea;
					}
				}
			}
 
			public override void ViewWillLayoutSubviews()
			{
				base.ViewWillLayoutSubviews();
 
				var childView = (Child?.Handler as IPlatformViewHandler)?.ViewController?.View;
 
				if (childView is not null)
				{
					childView.Frame = View.Bounds;
				}
			}
 
			public override void ViewDidLayoutSubviews()
			{
				base.ViewDidLayoutSubviews();
				UpdateFrames();
			}
 
			public override void ViewDidLoad()
			{
				base.ViewDidLoad();
 
				_tracker.Target = Child;
				_tracker.AdditionalTargets = Child.GetParentPages();
 
				UpdateToolbarItems();
			}
 
			public override void ViewWillAppear(bool animated)
			{
				SetupDefaultNavigationBarAppearance();
				UpdateNavigationBarVisibility(animated);
 
				NavigationRenderer n;
				var isTranslucent = false;
				if (_navigation.TryGetTarget(out n))
					isTranslucent = n.NavigationBar.Translucent;
				EdgesForExtendedLayout = isTranslucent ? UIRectEdge.All : UIRectEdge.None;
 
				base.ViewWillAppear(animated);
			}
 
			public override void WillMoveToParentViewController(UIViewController parent)
			{
				base.WillMoveToParentViewController(parent);
 
				if (parent is null)
				{
					_tracker.CollectionChanged -= TrackerOnCollectionChanged;
				}
				else
				{
					_tracker.CollectionChanged += TrackerOnCollectionChanged;
				}
			}
 
			internal void Disconnect(bool dispose)
			{
				if (Child is Page child)
				{
					child.SendDisappearing();
					child.PropertyChanged -= HandleChildPropertyChanged;
					Child = null;
				}
 
				if (_tracker is not null)
				{
					_tracker.Target = null;
					_tracker.CollectionChanged -= TrackerOnCollectionChanged;
					_tracker = null;
				}
 
				if (NavigationItem.TitleView is not null)
				{
					if (dispose)
						NavigationItem.TitleView.Dispose();
 
					NavigationItem.TitleView = null;
				}
 
				if (NavigationItem.RightBarButtonItems is not null && dispose)
				{
					for (var i = 0; i < NavigationItem.RightBarButtonItems.Length; i++)
						NavigationItem.RightBarButtonItems[i].Dispose();
				}
 
				if (ToolbarItems is not null && dispose)
				{
					for (var i = 0; i < ToolbarItems.Length; i++)
						ToolbarItems[i].Dispose();
				}
 
				for (int i = View.Subviews.Length - 1; i >= 0; i--)
				{
					View.Subviews[i].RemoveFromSuperview();
				}
 
 
				for (int i = ChildViewControllers.Length - 1; i >= 0; i--)
				{
					var childViewController = ChildViewControllers[i];
					childViewController.View.RemoveFromSuperview();
					childViewController.RemoveFromParentViewController();
				}
 
			}
 
			protected override void Dispose(bool disposing)
			{
				if (_disposed)
				{
					return;
				}
 
				_disposed = true;
 
				if (disposing)
				{
					Disconnect(true);
				}
 
				base.Dispose(disposing);
			}
 
			void HandleChildPropertyChanged(object sender, PropertyChangedEventArgs e)
			{
				if (e.PropertyName == NavigationPage.HasNavigationBarProperty.PropertyName)
					UpdateNavigationBarVisibility(true);
				else if (e.PropertyName == Page.TitleProperty.PropertyName)
					NavigationItem.Title = Child.Title;
				else if (e.PropertyName == NavigationPage.HasBackButtonProperty.PropertyName)
					UpdateHasBackButton();
				else if (e.PropertyName == PrefersStatusBarHiddenProperty.PropertyName)
					UpdatePrefersStatusBarHidden();
				else if (e.PropertyName == LargeTitleDisplayProperty.PropertyName)
					UpdateLargeTitles();
				else if (e.PropertyName == NavigationPage.TitleIconImageSourceProperty.PropertyName ||
					 e.PropertyName == NavigationPage.TitleViewProperty.PropertyName)
					UpdateTitleArea(Child);
				else if (e.PropertyName == NavigationPage.IconColorProperty.PropertyName)
					UpdateIconColor();
			}
 
			internal void SetupDefaultNavigationBarAppearance()
			{
				if (!(OperatingSystem.IsIOSVersionAtLeast(13) || OperatingSystem.IsMacCatalystVersionAtLeast(13)))
					return;
 
				if (!_navigation.TryGetTarget(out NavigationRenderer navigationRenderer))
					return;
 
				// We will use UINavigationBar.Appareance to infer settings that
				// were already set to navigation bar in older versions of
				// iOS.
				var navBar = navigationRenderer.NavigationBar;
				var navAppearance = navBar.StandardAppearance;
 
				if (navAppearance.BackgroundColor == null)
				{
					var backgroundColor = navBar.BarTintColor;
					navBar.CompactAppearance.BackgroundColor = navBar.StandardAppearance.BackgroundColor = navBar.ScrollEdgeAppearance.BackgroundColor = backgroundColor;
				}
 
				if (navAppearance.BackgroundImage == null)
				{
					var backgroundImage = navBar.GetBackgroundImage(UIBarMetrics.Default);
					navBar.CompactAppearance.BackgroundImage = navBar.StandardAppearance.BackgroundImage = navBar.ScrollEdgeAppearance.BackgroundImage = backgroundImage;
				}
 
				if (navAppearance.ShadowImage == null)
				{
					var shadowImage = navBar.ShadowImage;
					navBar.CompactAppearance.ShadowImage = navBar.StandardAppearance.ShadowImage = navBar.ScrollEdgeAppearance.ShadowImage = shadowImage;
 
					if (shadowImage != null && shadowImage.Size == SizeF.Empty)
						navBar.CompactAppearance.ShadowColor = navBar.StandardAppearance.ShadowColor = navBar.ScrollEdgeAppearance.ShadowColor = UIColor.Clear;
				}
 
				UIImage backIndicatorImage = navBar.BackIndicatorImage;
				UIImage backIndicatorTransitionMaskImage = navBar.BackIndicatorTransitionMaskImage;
 
				if (backIndicatorImage != null && backIndicatorImage.Size == SizeF.Empty)
					backIndicatorImage = GetEmptyBackIndicatorImage();
 
				if (backIndicatorTransitionMaskImage != null && backIndicatorTransitionMaskImage.Size == SizeF.Empty)
					backIndicatorTransitionMaskImage = GetEmptyBackIndicatorImage();
 
				navBar.CompactAppearance.SetBackIndicatorImage(backIndicatorImage, backIndicatorTransitionMaskImage);
				navBar.StandardAppearance.SetBackIndicatorImage(backIndicatorImage, backIndicatorTransitionMaskImage);
				navBar.ScrollEdgeAppearance.SetBackIndicatorImage(backIndicatorImage, backIndicatorTransitionMaskImage);
			}
 
			UIImage GetEmptyBackIndicatorImage()
			{
				var rect = RectangleF.Empty;
				var size = rect.Size;
 
				UIGraphics.BeginImageContext(size);
				var context = UIGraphics.GetCurrentContext();
				context?.SetFillColor(1, 1, 1, 0);
				context?.FillRect(rect);
 
				var empty = UIGraphics.GetImageFromCurrentImageContext();
				context?.Dispose();
 
				return empty;
			}
 
			public override void ViewWillTransitionToSize(SizeF toSize, IUIViewControllerTransitionCoordinator coordinator)
			{
				base.ViewWillTransitionToSize(toSize, coordinator);
 
				if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
					UpdateLeftBarButtonItem();
			}
 
			internal void UpdateLeftBarButtonItem(Page pageBeingRemoved = null)
			{
				NavigationRenderer n;
				if (!_navigation.TryGetTarget(out n))
					return;
 
				var currentChild = this.Child;
				var firstPage = n.NavPageController.Pages.FirstOrDefault();
 
 
				if (n._parentFlyoutPage == null)
					return;
 
				if (firstPage != pageBeingRemoved && currentChild != firstPage && NavigationPage.GetHasBackButton(currentChild))
				{
					NavigationItem.LeftBarButtonItem = null;
					return;
				}
 
				SetFlyoutLeftBarButton(this, n._parentFlyoutPage);
			}
 
 
			public bool NeedsTitleViewContainer(Page page) => NavigationPage.GetTitleIconImageSource(page) != null || NavigationPage.GetTitleView(page) != null;
 
			internal void UpdateBackButtonTitle(Page page) => UpdateBackButtonTitle(page.Title, NavigationPage.GetBackButtonTitle(page));
 
			internal void UpdateBackButtonTitle(string title, string backButtonTitle)
			{
				if (!string.IsNullOrWhiteSpace(title))
					NavigationItem.Title = title;
 
				if (backButtonTitle != null)
					// adding a custom event handler to UIBarButtonItem for navigating back seems to be ignored.
					NavigationItem.BackBarButtonItem = new UIBarButtonItem { Title = backButtonTitle, Style = UIBarButtonItemStyle.Plain };
				else
					NavigationItem.BackBarButtonItem = null;
			}
 
			internal void UpdateTitleArea(Page page)
			{
				if (page == null)
					return;
 
				ImageSource titleIcon = NavigationPage.GetTitleIconImageSource(page);
				View titleView = NavigationPage.GetTitleView(page);
				bool needContainer = titleView != null || titleIcon != null;
 
				string backButtonText = NavigationPage.GetBackButtonTitle(page);
				bool isBackButtonTextSet = page.IsSet(NavigationPage.BackButtonTitleProperty);
 
				// on iOS 10 if the user hasn't set the back button text
				// we set it to an empty string so it's consistent with iOS 11
				if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsMacCatalystVersionAtLeast(11)) && !isBackButtonTextSet)
					backButtonText = "";
 
				_navigation.TryGetTarget(out NavigationRenderer n);
 
				// First page and we have a flyout detail to contend with
				UpdateLeftBarButtonItem();
				UpdateBackButtonTitle(page.Title ?? n?.NavPage.Title, backButtonText);
 
				//var hadTitleView = NavigationItem.TitleView != null;
				ClearTitleViewContainer();
				if (needContainer)
				{
					if (n is null)
						return;
 
					Container titleViewContainer = new Container(titleView, n.NavigationBar);
 
					UpdateTitleImage(titleViewContainer, titleIcon);
					NavigationItem.TitleView = titleViewContainer;
				}
			}
 
			void UpdateIconColor()
			{
				if (_navigation.TryGetTarget(out NavigationRenderer navigationRenderer))
					navigationRenderer.UpdateBarTextColor();
			}
 
			void UpdateTitleImage(Container titleViewContainer, ImageSource titleIcon)
			{
				if (titleViewContainer == null)
					return;
 
				if (titleIcon == null || titleIcon.IsEmpty)
				{
					titleViewContainer.Icon = null;
				}
				else
				{
					if (_navigation.TryGetTarget(out NavigationRenderer n) && n.MauiContext is IMauiContext mc)
					{
						titleIcon.LoadImage(mc, result =>
						{
							var image = result?.Value;
							try
							{
								titleViewContainer.Icon = new UIImageView(image);
							}
							catch
							{
								//UIImage ctor throws on file not found if MonoTouch.ObjCRuntime.Class.ThrowOnInitFailure is true;
							}
						});
					}
				}
			}
 
			void ClearTitleViewContainer()
			{
				if (NavigationItem.TitleView != null && NavigationItem.TitleView is Container titleViewContainer)
				{
					titleViewContainer.Dispose();
					titleViewContainer = null;
					NavigationItem.TitleView = null;
				}
			}
 
			void UpdatePrefersStatusBarHidden()
			{
				View.SetNeedsLayout();
				ParentViewController?.View.SetNeedsLayout();
				SetNeedsStatusBarAppearanceUpdate();
			}
 
			void TrackerOnCollectionChanged(object sender, EventArgs eventArgs)
			{
				UpdateToolbarItems();
			}
 
			void UpdateHasBackButton()
			{
				if (Child is not Page child || NavigationItem.HidesBackButton == !NavigationPage.GetHasBackButton(child))
					return;
 
				NavigationItem.HidesBackButton = !NavigationPage.GetHasBackButton(child);
 
				NavigationRenderer n;
				if (!_navigation.TryGetTarget(out n))
					return;
 
				if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsMacCatalystVersionAtLeast(11)) || n._parentFlyoutPage != null)
					UpdateTitleArea(child);
			}
 
			void UpdateNavigationBarVisibility(bool animated)
			{
				var current = Child;
 
				if (current == null || NavigationController == null)
					return;
 
				var hasNavBar = NavigationPage.GetHasNavigationBar(current);
				if (!_navigation.TryGetTarget(out NavigationRenderer navigationRenderer))
				{
					navigationRenderer._hasNavigationBar = hasNavBar;
				}
 
				if (NavigationController.NavigationBarHidden == hasNavBar)
				{
					// prevent bottom content "jumping"
					current.IgnoresContainerArea = !hasNavBar;
					NavigationController.SetNavigationBarHidden(!hasNavBar, animated);
				}
			}
 
			void UpdateToolbarItems()
			{
				if (NavigationItem.RightBarButtonItems != null)
				{
					for (var i = 0; i < NavigationItem.RightBarButtonItems.Length; i++)
						NavigationItem.RightBarButtonItems[i].Dispose();
				}
				if (ToolbarItems != null)
				{
					for (var i = 0; i < ToolbarItems.Length; i++)
						ToolbarItems[i].Dispose();
				}
 
				List<UIBarButtonItem> primaries = null;
				List<UIBarButtonItem> secondaries = null;
				var toolbarItems = _tracker.ToolbarItems;
				foreach (var item in toolbarItems)
				{
					if (item.Order == ToolbarItemOrder.Secondary)
						(secondaries = secondaries ?? new List<UIBarButtonItem>()).Add(item.ToUIBarButtonItem(true));
					else
						(primaries = primaries ?? new List<UIBarButtonItem>()).Add(item.ToUIBarButtonItem());
				}
 
				if (primaries != null)
					primaries.Reverse();
				NavigationItem.SetRightBarButtonItems(primaries == null ? Array.Empty<UIBarButtonItem>() : primaries.ToArray(), false);
				ToolbarItems = secondaries == null ? Array.Empty<UIBarButtonItem>() : secondaries.ToArray();
 
				NavigationRenderer n;
				if (_navigation.TryGetTarget(out n))
					n.UpdateToolBarVisible();
			}
 
			void UpdateLargeTitles()
			{
				var page = Child;
				if (page != null && OperatingSystem.IsIOSVersionAtLeast(11))
				{
					var largeTitleDisplayMode = page.OnThisPlatform().LargeTitleDisplay();
					switch (largeTitleDisplayMode)
					{
						case LargeTitleDisplayMode.Always:
							NavigationItem.LargeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Always;
							break;
						case LargeTitleDisplayMode.Automatic:
							NavigationItem.LargeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Automatic;
							break;
						case LargeTitleDisplayMode.Never:
							NavigationItem.LargeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Never;
							break;
					}
				}
			}
 
			public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations()
			{
				if (Child?.Handler is IPlatformViewHandler ivh)
					return ivh.ViewController.GetSupportedInterfaceOrientations();
				return base.GetSupportedInterfaceOrientations();
			}
 
			public override UIInterfaceOrientation PreferredInterfaceOrientationForPresentation()
			{
				if (Child?.Handler is IPlatformViewHandler ivh)
					return ivh.ViewController.PreferredInterfaceOrientationForPresentation();
				return base.PreferredInterfaceOrientationForPresentation();
			}
#pragma warning disable CA1422 // Validate platform compatibility
			public override bool ShouldAutorotate()
			{
				if (Child?.Handler is IPlatformViewHandler ivh)
 
					return ivh.ViewController.ShouldAutorotate();
				return base.ShouldAutorotate();
			}
#pragma warning restore CA1422 // Validate platform compatibility
 
			[System.Runtime.Versioning.UnsupportedOSPlatform("ios6.0")]
			[System.Runtime.Versioning.UnsupportedOSPlatform("tvos")]
			public override bool ShouldAutorotateToInterfaceOrientation(UIInterfaceOrientation toInterfaceOrientation)
			{
				if (Child?.Handler is IPlatformViewHandler ivh)
					return ivh.ViewController.ShouldAutorotateToInterfaceOrientation(toInterfaceOrientation);
				return base.ShouldAutorotateToInterfaceOrientation(toInterfaceOrientation);
			}
 
			public override bool ShouldAutomaticallyForwardRotationMethods => true;
 
			public override async void DidMoveToParentViewController(UIViewController parent)
			{
				//we are being removed from the UINavigationPage
				if (parent == null)
				{
					NavigationRenderer navRenderer;
					if (_navigation.TryGetTarget(out navRenderer))
						await navRenderer.UpdateFormsInnerNavigation(Child);
				}
				base.DidMoveToParentViewController(parent);
			}
		}
 
		public override UIViewController ChildViewControllerForStatusBarHidden()
		{
			return (Current.Handler as IPlatformViewHandler)?.ViewController;
		}
 
		public override UIViewController ChildViewControllerForHomeIndicatorAutoHidden =>
			ChildViewControllerForStatusBarHidden();
 
		bool IViewHandler.HasContainer { get => false; set { } }
 
		object IViewHandler.ContainerView => null;
 
		IView IViewHandler.VirtualView => Element;
 
		object IElementHandler.PlatformView => NativeView;
 
		Maui.IElement IElementHandler.VirtualView => Element;
 
		IMauiContext IElementHandler.MauiContext => _mauiContext;
 
		UIView IPlatformViewHandler.PlatformView => NativeView;
 
		UIView IPlatformViewHandler.ContainerView => null;
 
		UIViewController IPlatformViewHandler.ViewController => this;
 
		IStackNavigationView INavigationViewHandler.VirtualView => NavPage;
 
		UIView INavigationViewHandler.PlatformView => NativeView;
 
		Size IViewHandler.GetDesiredSize(double widthConstraint, double heightConstraint) =>
			_viewHandlerWrapper.GetDesiredSize(widthConstraint, heightConstraint);
 
		void IViewHandler.PlatformArrange(Rect rect) =>
			_viewHandlerWrapper.PlatformArrange(rect);
 
		void IElementHandler.SetMauiContext(IMauiContext mauiContext)
		{
			_mauiContext = mauiContext;
		}
 
		void IElementHandler.SetVirtualView(Maui.IElement view)
		{
			_viewHandlerWrapper.SetVirtualView(view, ElementChanged, false);
			_element = view is VisualElement v ? new(v) : null;
 
			void ElementChanged(ElementChangedEventArgs<NavigationPage> e)
			{
				OnElementChanged(new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
			}
		}
 
		void IElementHandler.UpdateValue(string property)
		{
			_viewHandlerWrapper.UpdateProperty(property);
		}
 
		void IElementHandler.Invoke(string command, object args)
		{
			_viewHandlerWrapper.Invoke(command, args);
		}
 
		void IElementHandler.DisconnectHandler()
		{
			_viewHandlerWrapper.DisconnectHandler();
		}
 
		internal class MauiControlsNavigationBar : UINavigationBar
		{
			[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
			public MauiControlsNavigationBar() : base()
			{
			}
 
			[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
			public MauiControlsNavigationBar(Foundation.NSCoder coder) : base(coder)
			{
			}
 
			[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
			protected MauiControlsNavigationBar(Foundation.NSObjectFlag t) : base(t)
			{
			}
 
			[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
			protected internal MauiControlsNavigationBar(IntPtr handle) : base(handle)
			{
			}
 
			[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
			public MauiControlsNavigationBar(RectangleF frame) : base(frame)
			{
			}
 
			protected internal MauiControlsNavigationBar(NativeHandle handle) : base(handle)
			{
			}
 
			public RectangleF BackButtonFrameSize { get; private set; }
			public UILabel NavBarLabel { get; private set; }
 
			public override void LayoutSubviews()
			{
				if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsMacCatalystVersionAtLeast(11)))
				{
					for (int i = 0; i < this.Subviews.Length; i++)
					{
						if (Subviews[i] is UIView view)
						{
							if (view.Class.Name == "_UINavigationBarBackIndicatorView")
							{
								if (view.Alpha == 0)
									BackButtonFrameSize = CGRect.Empty;
								else
									BackButtonFrameSize = view.Frame;
 
								break;
							}
							else if (view.Class.Name == "UINavigationItemButtonView")
							{
								if (view.Subviews.Length == 0)
									NavBarLabel = null;
								else if (view.Subviews[0] is UILabel titleLabel)
									NavBarLabel = titleLabel;
							}
						}
					}
				}
 
				base.LayoutSubviews();
			}
		}
 
		class Container : UIView
		{
			View _view;
			MauiControlsNavigationBar _bar;
			IPlatformViewHandler _child;
			UIImageView _icon;
			bool _disposed;
 
			public Container(View view, UINavigationBar bar) : base(bar.Bounds)
			{
				if (OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsMacCatalystVersionAtLeast(11))
				{
					TranslatesAutoresizingMaskIntoConstraints = false;
				}
				else
				{
					TranslatesAutoresizingMaskIntoConstraints = true;
					AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth;
				}
 
				_bar = bar as MauiControlsNavigationBar;
				if (view != null)
				{
					_view = view;
 
					if (_view.Parent is null)
					{
						_view.ParentSet += OnTitleViewParentSet;
					}
					else
					{
						SetupTitleView();
					}
				}
 
				ClipsToBounds = true;
			}
 
			void OnTitleViewParentSet(object sender, EventArgs e)
			{
				if (sender is View view)
					view.ParentSet -= OnTitleViewParentSet;
 
				SetupTitleView();
			}
 
			void SetupTitleView()
			{
				var mauiContext = _view.FindMauiContext();
				if (_view is not null && mauiContext is not null)
				{
					var platformView = _view.ToPlatform(mauiContext);
					_child = (IPlatformViewHandler)_view.Handler;
					AddSubview(platformView);
				}
 
			}
 
			public override CGSize IntrinsicContentSize => UILayoutFittingExpandedSize;
 
			nfloat IconHeight => _icon?.Frame.Height ?? 0;
			nfloat IconWidth => _icon?.Frame.Width ?? 0;
 
			// Navigation bar will not stretch past these values. Prevent content clipping.
			// iOS11 does this for us automatically, but apparently iOS10 doesn't.
			nfloat ToolbarHeight
			{
				get
				{
					if (Superview?.Bounds.Height > 0)
						return Superview.Bounds.Height;
 
					return (DeviceInfo.Idiom == DeviceIdiom.Phone && DeviceDisplay.MainDisplayInfo.Orientation.IsLandscape()) ? 32 : 44;
				}
			}
 
			public override CGRect Frame
			{
				get => base.Frame;
				set
				{
					if (Superview != null)
					{
						if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsMacCatalystVersionAtLeast(11)))
						{
							value.Y = Superview.Bounds.Y;
 
							if (_bar != null && String.IsNullOrWhiteSpace(_bar.NavBarLabel?.Text) && _bar.BackButtonFrameSize != RectangleF.Empty)
							{
								var xSpace = _bar.BackButtonFrameSize.Width + (_bar.BackButtonFrameSize.X * 2);
								value.Width = (value.X - xSpace) + value.Width;
								value.X = xSpace;
							}
						};
 
						value.Height = ToolbarHeight;
					}
 
					base.Frame = value;
				}
			}
 
			public UIImageView Icon
			{
				set
				{
					if (_icon != null)
						_icon.RemoveFromSuperview();
 
					_icon = value;
 
					if (_icon != null)
						AddSubview(_icon);
				}
			}
 
			public override SizeF SizeThatFits(SizeF size)
			{
				return new SizeF(size.Width, ToolbarHeight);
			}
 
			public override void LayoutSubviews()
			{
				base.LayoutSubviews();
				if (Frame == CGRect.Empty || Frame.Width >= 10000 || Frame.Height >= 10000)
					return;
 
				nfloat toolbarHeight = ToolbarHeight;
 
				double height = Math.Min(toolbarHeight, Bounds.Height);
 
				if (_icon != null)
					_icon.Frame = new RectangleF(0, 0, IconWidth, Math.Min(toolbarHeight, IconHeight));
 
				if (_child?.VirtualView != null)
				{
					Rect layoutBounds = new Rect(IconWidth, 0, Bounds.Width - IconWidth, height);
 
					_child.PlatformArrangeHandler(layoutBounds);
				}
				else if (_icon != null && Superview != null)
				{
					_icon.Center = new PointF(Superview.Frame.Width / 2 - Frame.X, Superview.Frame.Height / 2);
				}
			}
 
			protected override void Dispose(bool disposing)
			{
				if (_disposed)
				{
					return;
				}
 
				_disposed = true;
 
				if (disposing)
				{
 
					if (_child?.IsConnected() == true)
					{
						(_child.ContainerView ?? _child.PlatformView).RemoveFromSuperview();
						_child.DisconnectHandler();
						_child = null;
					}
 
					if (_view is not null)
					{
						_view.ParentSet -= OnTitleViewParentSet;
					}
 
					_view = null;
 
					_icon?.Dispose();
					_icon = null;
				}
 
				base.Dispose(disposing);
			}
		}
	}
}