File: Compatibility\Handlers\Shell\iOS\ShellPageRendererTracker.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.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using System.Windows.Input;
using CoreGraphics;
using Foundation;
using Microsoft.Extensions.Logging;
using ObjCRuntime;
using UIKit;
using static Microsoft.Maui.Controls.Compatibility.Platform.iOS.AccessibilityExtensions;
using static Microsoft.Maui.Controls.Compatibility.Platform.iOS.ToolbarItemExtensions;
 
namespace Microsoft.Maui.Controls.Platform.Compatibility
{
	public class ShellPageRendererTracker : IShellPageRendererTracker, IFlyoutBehaviorObserver
	{
		#region IShellPageRendererTracker
 
		public bool IsRootPage { get; set; }
 
		public UIViewController ViewController
		{
			get
			{
				_rendererRef.TryGetTarget(out var target);
				return target;
			}
			set
			{
				_rendererRef = new WeakReference<UIViewController>(value);
				OnRendererSet();
			}
		}
 
		public Page Page
		{
			get { return _page; }
			set
			{
				if (_page == value)
					return;
 
				var oldPage = _page;
				_page = value;
 
				OnPageSet(oldPage, _page);
			}
		}
 
		#endregion IShellPageRendererTracker
 
		IShellContext _context;
		bool _disposed;
		FlyoutBehavior _flyoutBehavior;
		WeakReference<UIViewController> _rendererRef;
		IShellSearchResultsRenderer _resultsRenderer;
		UISearchController _searchController;
		SearchHandler _searchHandler;
		Page _page;
		NSCache _nSCache;
		SearchHandlerAppearanceTracker _searchHandlerAppearanceTracker;
		IFontManager _fontManager;
		bool _isVisiblePage;
 
		BackButtonBehavior BackButtonBehavior { get; set; }
		UINavigationItem NavigationItem { get; set; }
		IMauiContext MauiContext => Page?.FindMauiContext() ?? _context.Shell.FindMauiContext();
 
		public ShellPageRendererTracker(IShellContext context)
		{
			_context = context;
			_nSCache = new NSCache();
			_context.Shell.PropertyChanged += HandleShellPropertyChanged;
 
			_fontManager = context.Shell.RequireFontManager();
		}
 
		public void OnFlyoutBehaviorChanged(FlyoutBehavior behavior)
		{
			_flyoutBehavior = behavior;
			UpdateToolbarItemsInternal();
		}
 
		protected virtual void HandleShellPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.Is(VisualElement.FlowDirectionProperty))
				UpdateFlowDirection();
			else if (e.Is(Shell.FlyoutIconProperty))
				UpdateLeftToolbarItems();
		}
 
		protected virtual void OnBackButtonBehaviorPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == BackButtonBehavior.CommandParameterProperty.PropertyName)
				return;
			else if (e.PropertyName == BackButtonBehavior.IsEnabledProperty.PropertyName)
			{
				if (NavigationItem?.LeftBarButtonItem != null)
					NavigationItem.LeftBarButtonItem.Enabled = BackButtonBehavior.IsEnabled;
 
				return;
			}
 
			UpdateLeftToolbarItems();
		}
 
		protected virtual void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == Shell.BackButtonBehaviorProperty.PropertyName)
			{
				SetBackButtonBehavior(Shell.GetBackButtonBehavior(Page));
			}
			else if (e.PropertyName == Shell.SearchHandlerProperty.PropertyName)
			{
				SearchHandler = Shell.GetSearchHandler(Page);
			}
			else if (e.IsOneOf(Shell.TitleViewProperty, VisualElement.HeightProperty, VisualElement.WidthProperty))
			{
				UpdateTitleView();
			}
			else if (e.PropertyName == Page.TitleProperty.PropertyName)
			{
				UpdateTitle();
			}
			else if (e.PropertyName == Shell.TabBarIsVisibleProperty.PropertyName)
			{
				UpdateTabBarVisible();
			}
		}
 
		protected virtual void UpdateTabBarVisible()
		{
			var tabBarVisible =
				(Page.FindParentOfType<ShellItem>() as IShellItemController)?.ShowTabs ?? Shell.GetTabBarIsVisible(Page);
 
			ViewController.HidesBottomBarWhenPushed = !tabBarVisible;
		}
 
		void OnToolbarPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (!ToolbarReady())
				return;
 
			if (e.PropertyName == Shell.TitleViewProperty.PropertyName)
			{
				UpdateTitleView();
			}
			else if (e.PropertyName == Page.TitleProperty.PropertyName)
			{
				UpdateTitle();
			}
		}
 
		protected virtual void UpdateTitle()
		{
			if (!ToolbarReady())
				return;
 
			NavigationItem.Title = _context.Shell.Toolbar.Title;
		}
 
 
		bool ToolbarReady()
		{
			if (_context.Shell.Toolbar is ShellToolbar st)
				return st.CurrentPage == Page;
 
			return _isVisiblePage;
		}
 
		void UpdateShellToMyPage()
		{
			if (Page == null)
				return;
 
			SetBackButtonBehavior(Shell.GetBackButtonBehavior(Page));
			SearchHandler = Shell.GetSearchHandler(Page);
			UpdateTitleView();
			UpdateTitle();
			UpdateTabBarVisible();
			UpdateToolbarItemsInternal();
		}
 
		protected virtual void OnPageSet(Page oldPage, Page newPage)
		{
			if (oldPage != null)
			{
				oldPage.Appearing -= PageAppearing;
				oldPage.Disappearing -= PageDisappearing;
				oldPage.PropertyChanged -= OnPagePropertyChanged;
				oldPage.Loaded -= OnPageLoaded;
				((INotifyCollectionChanged)oldPage.ToolbarItems).CollectionChanged -= OnToolbarItemsChanged;
			}
 
			if (newPage != null)
			{
				newPage.Appearing += PageAppearing;
				newPage.Disappearing += PageDisappearing;
				newPage.PropertyChanged += OnPagePropertyChanged;
 
				if (!newPage.IsLoaded)
					newPage.Loaded += OnPageLoaded;
 
				((INotifyCollectionChanged)newPage.ToolbarItems).CollectionChanged += OnToolbarItemsChanged;
				CheckAppeared();
 
				if (oldPage == null)
				{
					((IShellController)_context.Shell).AddFlyoutBehaviorObserver(this);
				}
			}
			else if (newPage == null && _context?.Shell is IShellController shellController)
			{
				shellController.RemoveFlyoutBehaviorObserver(this);
			}
		}
 
		protected virtual void OnRendererSet()
		{
			NavigationItem = ViewController.NavigationItem;
 
			if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsTvOSVersionAtLeast(11)))
			{
				ViewController.AutomaticallyAdjustsScrollViewInsets = false;
			}
		}
 
		protected virtual void UpdateTitleView()
		{
			if (!ToolbarReady())
				return;
 
			var titleView = _context.Shell.Toolbar.TitleView as View;
 
			if (NavigationItem.TitleView is TitleViewContainer tvc &&
				tvc.View == titleView)
			{
				// The MauiContext/handler/other may have changed on the `View`
				// This tells the title view container to make sure
				// the currently added platformview is still valid and doesn't need
				// to be recreated
				tvc.UpdatePlatformView();
				return;
			}
 
			if (titleView == null)
			{
				var view = NavigationItem.TitleView;
				NavigationItem.TitleView = null;
				view?.Dispose();
			}
			else
			{
				if (titleView.Parent != null)
				{
					var view = new TitleViewContainer(titleView);
					NavigationItem.TitleView = view;
				}
				else
				{
					titleView.ParentSet += OnTitleViewParentSet;
				}
			}
		}
 
		void OnTitleViewParentSet(object sender, EventArgs e)
		{
			((Element)sender).ParentSet -= OnTitleViewParentSet;
			UpdateTitleView();
		}
 
		internal void UpdateToolbarItemsInternal(bool updateWhenLoaded = true)
		{
			if (updateWhenLoaded && Page.IsLoaded || !updateWhenLoaded)
				UpdateToolbarItems();
		}
 
		protected virtual void UpdateToolbarItems()
		{
			if (NavigationItem == null)
			{
				return;
			}
 
			if (NavigationItem.RightBarButtonItems != null)
			{
				for (var i = 0; i < NavigationItem.RightBarButtonItems.Length; i++)
					NavigationItem.RightBarButtonItems[i].Dispose();
			}
 
			var shellToolbarItems = _context?.Shell?.ToolbarItems;
			List<UIBarButtonItem> primaries = null;
			if (Page.ToolbarItems.Count > 0) // Display toolbar items defined on the current page
			{
				foreach (var item in System.Linq.Enumerable.OrderBy(Page.ToolbarItems, x => x.Priority))
				{
					(primaries = primaries ?? new List<UIBarButtonItem>()).Add(item.ToUIBarButtonItem(false, true));
				}
			}
			else if (shellToolbarItems != null && shellToolbarItems.Count > 0) // If the page has no toolbar items use the ones defined for the shell
			{
				foreach (var item in System.Linq.Enumerable.OrderBy(shellToolbarItems, x => x.Priority))
				{
					(primaries = primaries ?? new List<UIBarButtonItem>()).Add(item.ToUIBarButtonItem(false, true));
				}
			}
 
			if (primaries != null)
				primaries.Reverse();
 
			NavigationItem.SetRightBarButtonItems(primaries == null ? Array.Empty<UIBarButtonItem>() : primaries.ToArray(), false);
 
			UpdateLeftToolbarItems();
		}
 
		void UpdateLeftToolbarItems()
		{
			var behavior = BackButtonBehavior;
 
			var image = behavior.GetPropertyIfSet<ImageSource>(BackButtonBehavior.IconOverrideProperty, null);
			var enabled = behavior.GetPropertyIfSet(BackButtonBehavior.IsEnabledProperty, true);
			var text = behavior.GetPropertyIfSet<string>(BackButtonBehavior.TextOverrideProperty, null);
			var command = behavior.GetPropertyIfSet<object>(BackButtonBehavior.CommandProperty, null);
			var backButtonVisible = behavior.GetPropertyIfSet<bool>(BackButtonBehavior.IsVisibleProperty, true);
 
			if (String.IsNullOrWhiteSpace(text) && image == null)
			{
				image = _context.Shell.FlyoutIcon;
			}
 
			if (!IsRootPage)
			{
				NavigationItem.HidesBackButton = !backButtonVisible;
				image = backButtonVisible ? image : null;
			}
 
			image.LoadImage(MauiContext, result =>
			{
				UIImage icon = null;
 
				if (image != null)
				{
					icon = result?.Value;
				}
				else if (String.IsNullOrWhiteSpace(text) && IsRootPage && _flyoutBehavior == FlyoutBehavior.Flyout)
				{
					icon = DrawHamburger();
				}
 
				if (icon != null)
				{
					NavigationItem.LeftBarButtonItem =
						new UIBarButtonItem(icon, UIBarButtonItemStyle.Plain, (s, e) => LeftBarButtonItemHandler(ViewController, IsRootPage)) { Enabled = enabled };
				}
				else
				{
					NavigationItem.LeftBarButtonItem = null;
					UpdateBackButtonTitle();
				}
 
				if (NavigationItem.LeftBarButtonItem != null)
				{
					if (String.IsNullOrWhiteSpace(image?.AutomationId))
					{
						if (IsRootPage)
						{
							NavigationItem.LeftBarButtonItem.AccessibilityIdentifier = "OK";
							NavigationItem.LeftBarButtonItem.AccessibilityLabel = "Menu";
						}
						else
							NavigationItem.LeftBarButtonItem.AccessibilityIdentifier = "Back";
					}
					else
					{
						NavigationItem.LeftBarButtonItem.AccessibilityIdentifier = image.AutomationId;
					}
 
					if (image != null)
					{
						NavigationItem.LeftBarButtonItem.SetAccessibilityHint(image);
						NavigationItem.LeftBarButtonItem.SetAccessibilityLabel(image);
					}
				}
			});
 
			UpdateBackButtonTitle();
		}
 
 
		void UpdateBackButtonTitle()
		{
			var behavior = BackButtonBehavior;
			var text = behavior.GetPropertyIfSet<string>(BackButtonBehavior.TextOverrideProperty, null);
 
			var navController = ViewController?.NavigationController;
 
			if (navController != null)
			{
				var viewControllers = ViewController.NavigationController.ViewControllers;
				var count = viewControllers.Length;
 
				if (count > 1 && viewControllers[count - 1] == ViewController)
				{
					var previousNavItem = viewControllers[count - 2].NavigationItem;
					if (previousNavItem != null)
					{
						if (text is not null)
						{
							var barButtonItem = (previousNavItem.BackBarButtonItem ??= new UIBarButtonItem());
							barButtonItem.Title = text;
						}
						else if (previousNavItem.BackBarButtonItem != null)
						{
							previousNavItem.BackBarButtonItem = null;
						}
					}
				}
			}
		}
 
		void LeftBarButtonItemHandler(UIViewController controller, bool isRootPage)
		{
			var behavior = BackButtonBehavior;
 
			var command = behavior.GetPropertyIfSet<ICommand>(BackButtonBehavior.CommandProperty, null);
			var commandParameter = behavior.GetPropertyIfSet<object>(BackButtonBehavior.CommandParameterProperty, null);
 
			if (command != null)
			{
				command.Execute(commandParameter);
			}
			else if (!isRootPage)
			{
				if (controller?.ParentViewController is ShellSectionRenderer ssr)
					ssr.SendPop();
				else if (controller?.ParentViewController is UINavigationController navigationController)
					navigationController.PopViewController(true);
			}
			else if (_flyoutBehavior == FlyoutBehavior.Flyout)
			{
				_context.Shell.SetValueFromRenderer(Shell.FlyoutIsPresentedProperty, true);
			}
		}
 
 
		UIImage DrawHamburger()
		{
			const string hamburgerKey = "Hamburger";
			UIImage img = (UIImage)_nSCache.ObjectForKey((NSString)hamburgerKey);
 
			if (img != null)
				return img;
 
			var rect = new CGRect(0, 0, 23f, 23f);
 
			UIGraphics.BeginImageContextWithOptions(rect.Size, false, 0);
			var ctx = UIGraphics.GetCurrentContext();
			ctx.SaveState();
			ctx.SetStrokeColor(UIColor.Blue.CGColor);
 
			float size = 3f;
			float start = 4f;
			ctx.SetLineWidth(size);
 
			for (int i = 0; i < 3; i++)
			{
				ctx.MoveTo(1f, start + i * (size * 2));
				ctx.AddLineToPoint(22f, start + i * (size * 2));
				ctx.StrokePath();
			}
 
			ctx.RestoreState();
			img = UIGraphics.GetImageFromCurrentImageContext();
			UIGraphics.EndImageContext();
 
			_nSCache.SetObjectforKey(img, (NSString)hamburgerKey);
			return img;
		}
 
		void OnToolbarItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			UpdateToolbarItemsInternal();
		}
 
		void SetBackButtonBehavior(BackButtonBehavior value)
		{
			if (BackButtonBehavior == value)
				return;
 
			if (BackButtonBehavior != null)
				BackButtonBehavior.PropertyChanged -= OnBackButtonBehaviorPropertyChanged;
 
			BackButtonBehavior = value;
 
			if (BackButtonBehavior != null)
				BackButtonBehavior.PropertyChanged += OnBackButtonBehaviorPropertyChanged;
 
			UpdateToolbarItemsInternal();
		}
 
		void OnBackButtonCommandCanExecuteChanged(object sender, EventArgs e)
		{
			if (NavigationItem?.LeftBarButtonItem == null)
				return;
 
			bool isEnabled = BackButtonBehavior.GetPropertyIfSet<bool>(BackButtonBehavior.IsEnabledProperty, true);
 
			if (isEnabled && sender is ICommand command)
				isEnabled = command.CanExecute(BackButtonBehavior?.CommandParameter);
 
			NavigationItem.LeftBarButtonItem.Enabled = isEnabled;
		}
 
		public class TitleViewContainer : UIContainerView
		{
			public TitleViewContainer(View view) : base(view)
			{
				MatchHeight = true;
 
				if (OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsTvOSVersionAtLeast(11))
				{
					TranslatesAutoresizingMaskIntoConstraints = false;
				}
				else
				{
					TranslatesAutoresizingMaskIntoConstraints = true;
					AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth;
				}
			}
 
			public override CGRect Frame
			{
				get => base.Frame;
				set
				{
					if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsTvOSVersionAtLeast(11)) && Superview != null)
					{
						value.Y = Superview.Bounds.Y;
						value.Height = Superview.Bounds.Height;
					}
 
					base.Frame = value;
				}
			}
 
			public override void LayoutSubviews()
			{
				UpdateFrame(Superview);
				base.LayoutSubviews();
			}
 
			public override void WillMoveToSuperview(UIView newSuper)
			{
				UpdateFrame(newSuper);
				base.WillMoveToSuperview(newSuper);
			}
 
			void UpdateFrame(UIView newSuper)
			{
				if (newSuper is not null && newSuper.Bounds != CGRect.Empty)
				{
					Height = newSuper.Bounds.Height;
				}
			}
 
			public override CGSize IntrinsicContentSize => UILayoutFittingExpandedSize;
 
			public override CGSize SizeThatFits(CGSize size)
			{
				return size;
			}
		}
 
		#region SearchHandler
 
		SearchHandler SearchHandler
		{
			get { return _searchHandler; }
			set
			{
				if (_searchHandler == value)
					return;
 
				if (_searchHandler != null)
				{
					if (_resultsRenderer != null)
					{
						_resultsRenderer.ItemSelected -= OnSearchItemSelected;
						_resultsRenderer.Dispose();
						_resultsRenderer = null;
					}
					_searchHandler.PropertyChanged -= OnSearchHandlerPropertyChanged;
					DettachSearchController();
				}
 
				_searchHandler = value;
 
				if (_searchHandler != null)
				{
					_searchHandler.PropertyChanged += OnSearchHandlerPropertyChanged;
					AttachSearchController();
				}
			}
		}
 
		protected virtual void OnSearchHandlerPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == SearchHandler.ClearPlaceholderEnabledProperty.PropertyName)
				_searchController.SearchBar.ShowsBookmarkButton = _searchHandler.ClearPlaceholderEnabled;
			else if (e.PropertyName == SearchHandler.SearchBoxVisibilityProperty.PropertyName)
				UpdateSearchVisibility(_searchController);
			else if (e.PropertyName == SearchHandler.IsSearchEnabledProperty.PropertyName)
				UpdateSearchIsEnabled(_searchController);
			else if (e.Is(SearchHandler.AutomationIdProperty))
			{
				UpdateAutomationId();
			}
		}
 
		void UpdateAutomationId()
		{
			if (_searchHandler?.AutomationId != null && _searchController?.SearchBar != null)
				_searchController.SearchBar.AccessibilityIdentifier = _searchHandler.AutomationId;
		}
 
		[SupportedOSPlatform("ios11.0")]
		protected virtual void RemoveSearchController(UINavigationItem navigationItem)
		{
			navigationItem.SearchController = null;
 
			// And now that we have removed the search controller we must perform the sacred dance
			// handed down from code-dancer to code-dancer. Yes this dance is to ensure the SafeAreaInsets
			// update since they do not update reliably without doing this.
 
			// We prefer a verticle down jiggle since it is least likely to produce a visual artifact
 
			if (ViewController?.View != null)
			{
				var oldFrame = ViewController.View.Frame;
				ViewController.View.Frame = new CGRect(oldFrame.X, oldFrame.Y, oldFrame.Width, oldFrame.Height + 1);
				ViewController.View.Frame = oldFrame;
			}
		}
 
		protected virtual void UpdateSearchIsEnabled(UISearchController searchController)
		{
			searchController.SearchBar.UserInteractionEnabled = SearchHandler.IsSearchEnabled;
		}
 
		protected virtual void UpdateSearchVisibility(UISearchController searchController)
		{
			var visibility = SearchHandler.SearchBoxVisibility;
			if (visibility == SearchBoxVisibility.Hidden)
			{
				if (searchController != null)
				{
					if (OperatingSystem.IsIOSVersionAtLeast(11))
						RemoveSearchController(NavigationItem);
					else
						NavigationItem.TitleView = null;
				}
			}
			else if (visibility == SearchBoxVisibility.Collapsible || visibility == SearchBoxVisibility.Expanded)
			{
				if (OperatingSystem.IsIOSVersionAtLeast(11))
				{
					NavigationItem.SearchController = _searchController;
					NavigationItem.HidesSearchBarWhenScrolling = visibility == SearchBoxVisibility.Collapsible;
				}
				else
				{
					NavigationItem.TitleView = _searchController.SearchBar;
				}
			}
		}
 
		void UpdateFlowDirection()
		{
			if (_searchHandlerAppearanceTracker != null)
			{
				_searchHandlerAppearanceTracker.UpdateFlowDirection(_context.Shell);
			}
			if (_searchController != null)
			{
				_searchController.View.UpdateFlowDirection(_context.Shell);
				_searchController.SearchBar.UpdateFlowDirection(_context.Shell);
			}
		}
 
		void AttachSearchController()
		{
 
			if (SearchHandler.ShowsResults)
			{
				_resultsRenderer = _context.CreateShellSearchResultsRenderer();
				_resultsRenderer.ItemSelected += OnSearchItemSelected;
				_resultsRenderer.SearchHandler = _searchHandler;
				ViewController.DefinesPresentationContext = true;
			}
 
			_searchController = new UISearchController(_resultsRenderer?.ViewController);
			var visibility = SearchHandler.SearchBoxVisibility;
			if (visibility != SearchBoxVisibility.Hidden)
			{
				if (OperatingSystem.IsIOSVersionAtLeast(11))
					NavigationItem.SearchController = _searchController;
				else
					NavigationItem.TitleView = _searchController.SearchBar;
			}
 
			var searchBar = _searchController.SearchBar;
 
			_searchController.SetSearchResultsUpdater(sc =>
			{
				SearchHandler.SetValue(SearchHandler.QueryProperty, sc.SearchBar.Text);
			});
 
			searchBar.BookmarkButtonClicked += BookmarkButtonClicked;
 
			searchBar.Placeholder = SearchHandler.Placeholder;
			UpdateSearchIsEnabled(_searchController);
			searchBar.SearchButtonClicked += SearchButtonClicked;
			if (OperatingSystem.IsIOSVersionAtLeast(11))
				NavigationItem.HidesSearchBarWhenScrolling = visibility == SearchBoxVisibility.Collapsible;
 
			var icon = SearchHandler.QueryIcon;
			if (icon != null)
			{
				SetSearchBarIcon(searchBar, icon, UISearchBarIcon.Search);
			}
 
			icon = SearchHandler.ClearIcon;
			if (icon != null)
			{
				SetSearchBarIcon(searchBar, icon, UISearchBarIcon.Clear);
			}
 
			icon = SearchHandler.ClearPlaceholderIcon;
			if (icon != null)
			{
				SetSearchBarIcon(searchBar, icon, UISearchBarIcon.Bookmark);
			}
 
			searchBar.ShowsBookmarkButton = SearchHandler.ClearPlaceholderEnabled;
 
			_searchHandlerAppearanceTracker = new SearchHandlerAppearanceTracker(searchBar, SearchHandler, _fontManager);
 
			UpdateFlowDirection();
			UpdateAutomationId();
		}
 
		void BookmarkButtonClicked(object sender, EventArgs e)
		{
			((ISearchHandlerController)SearchHandler).ClearPlaceholderClicked();
		}
 
		void DettachSearchController()
		{
			_searchHandlerAppearanceTracker.Dispose();
			_searchHandlerAppearanceTracker = null;
			if (OperatingSystem.IsIOSVersionAtLeast(11))
			{
				RemoveSearchController(NavigationItem);
			}
			else
			{
				NavigationItem.TitleView = null;
			}
 
			_searchController.SetSearchResultsUpdater(null);
			_searchController.Dispose();
			_searchController = null;
		}
 
		void OnSearchItemSelected(object sender, object e)
		{
			_searchController.Active = false;
			((ISearchHandlerController)SearchHandler).ItemSelected(e);
		}
 
		void SearchButtonClicked(object sender, EventArgs e)
		{
			((ISearchHandlerController)SearchHandler).QueryConfirmed();
		}
 
		void SetSearchBarIcon(UISearchBar searchBar, ImageSource source, UISearchBarIcon icon)
		{
			source.LoadImage(source.FindMauiContext(), image =>
			{
				var result = image?.Value;
				if (result != null)
				{
					var newResult = result.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
					searchBar.SetImageforSearchBarIcon(newResult, icon, UIControlState.Normal);
					searchBar.SetImageforSearchBarIcon(newResult, icon, UIControlState.Highlighted);
					searchBar.SetImageforSearchBarIcon(newResult, icon, UIControlState.Selected);
				}
			});
		}
 
		void OnPageLoaded(object sender, EventArgs e)
		{
			if (sender is Page page)
				page.Loaded -= OnPageLoaded;
 
			// This means the user removed this page during the loaded event
			if (_page is null)
			{
				SetDisappeared();
				return;
			}
 
			UpdateToolbarItemsInternal();
			CheckAppeared();
		}
 
		void PageAppearing(object sender, EventArgs e) =>
			SetAppeared();
 
		void PageDisappearing(object sender, EventArgs e) =>
			SetDisappeared();
 
		void CheckAppeared()
		{
			if (_context.Shell.CurrentPage == Page)
				SetAppeared();
		}
 
		void SetAppeared()
		{
			if (_isVisiblePage)
				return;
 
			_isVisiblePage = true;
			//UIKIt will try to override our colors when the SearchController is inside the NavigationBar
			//Best way was to force them to be set again when page is Appearing / ViewDidLoad
			_searchHandlerAppearanceTracker?.UpdateSearchBarColors();
			UpdateShellToMyPage();
 
			if (_context.Shell.Toolbar != null)
				_context.Shell.Toolbar.PropertyChanged += OnToolbarPropertyChanged;
		}
 
		void SetDisappeared()
		{
			if (!_isVisiblePage)
				return;
 
			_isVisiblePage = false;
 
			// This will only be null if the user removes a shell page
			// while that shell page is loading.
			// When that happens this control will dispose and these
			// events will be cleaned up there
			if (_context?.Shell?.Toolbar is not null)
				_context.Shell.Toolbar.PropertyChanged -= OnToolbarPropertyChanged;
		}
 
		#endregion SearchHandler
 
		#region IDisposable Support
 
		public void Dispose()
		{
			Dispose(true);
		}
 
		protected virtual void Dispose(bool disposing)
		{
			if (_disposed)
				return;
 
			if (disposing)
			{
				_searchHandlerAppearanceTracker?.Dispose();
				Page.Loaded -= OnPageLoaded;
				Page.Appearing -= PageAppearing;
				Page.Disappearing -= PageDisappearing;
				Page.PropertyChanged -= OnPagePropertyChanged;
				((INotifyCollectionChanged)Page.ToolbarItems).CollectionChanged -= OnToolbarItemsChanged;
				((IShellController)_context.Shell).RemoveFlyoutBehaviorObserver(this);
 
				if (BackButtonBehavior != null)
					BackButtonBehavior.PropertyChanged -= OnBackButtonBehaviorPropertyChanged;
 
				_context.Shell.PropertyChanged -= HandleShellPropertyChanged;
 
				if (_context.Shell.Toolbar != null)
					_context.Shell.Toolbar.PropertyChanged -= OnToolbarPropertyChanged;
 
				if (NavigationItem?.TitleView is TitleViewContainer tvc)
					tvc.Disconnect();
			}
 
			_context = null;
			SearchHandler = null;
			Page = null;
			BackButtonBehavior = null;
			_rendererRef = null;
			NavigationItem = null;
			_searchHandlerAppearanceTracker = null;
			_disposed = true;
		}
		#endregion IDisposable Support
	}
}