File: Handlers\SearchBar\SearchBarHandler.iOS.cs
Web Access
Project: src\src\Core\src\Core.csproj (Microsoft.Maui)
using System;
using Foundation;
using Microsoft.Maui.Graphics;
using UIKit;
 
namespace Microsoft.Maui.Handlers
{
	public partial class SearchBarHandler : ViewHandler<ISearchBar, MauiSearchBar>
	{
		UITextField? _editor;
		readonly MauiSearchBarProxy _proxy = new();
 
		public UITextField? QueryEditor => _editor;
 
		protected override MauiSearchBar CreatePlatformView()
		{
			var searchBar = new MauiSearchBar() { BarStyle = UIBarStyle.Default };
 
			_editor = searchBar.GetSearchTextField();
 
 
			return searchBar;
		}
 
		protected override void ConnectHandler(MauiSearchBar platformView)
		{
			_proxy.Connect(this, VirtualView, platformView);
 
			base.ConnectHandler(platformView);
		}
 
		protected override void DisconnectHandler(MauiSearchBar platformView)
		{
			_proxy.Disconnect(platformView, _editor);
 
			base.DisconnectHandler(platformView);
		}
 
		public override Size GetDesiredSize(double widthConstraint, double heightConstraint)
		{
			if (double.IsInfinity(widthConstraint) || double.IsInfinity(heightConstraint))
			{
				PlatformView.SizeToFit();
				return new Size(PlatformView.Frame.Width, PlatformView.Frame.Height);
			}
 
			return base.GetDesiredSize(widthConstraint, heightConstraint);
		}
 
		public static void MapBackground(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateBackground(searchBar);
		}
 
		public static void MapIsEnabled(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateIsEnabled(searchBar);
		}
 
		public static void MapText(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateText(searchBar);
 
			// Any text update requires that we update any attributed string formatting
			MapFormatting(handler, searchBar);
		}
 
		public static void MapPlaceholder(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdatePlaceholder(searchBar, handler.QueryEditor);
		}
 
		public static void MapPlaceholderColor(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdatePlaceholder(searchBar, handler.QueryEditor);
		}
 
		public static void MapFont(ISearchBarHandler handler, ISearchBar searchBar)
		{
			var fontManager = handler.GetRequiredService<IFontManager>();
 
			handler.QueryEditor?.UpdateFont(searchBar, fontManager);
		}
 
		public static void MapHorizontalTextAlignment(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.QueryEditor?.UpdateHorizontalTextAlignment(searchBar);
		}
 
		public static void MapVerticalTextAlignment(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateVerticalTextAlignment(searchBar, handler?.QueryEditor);
		}
 
		public static void MapCharacterSpacing(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.QueryEditor?.UpdateCharacterSpacing(searchBar);
		}
 
		public static void MapFormatting(ISearchBarHandler handler, ISearchBar searchBar)
		{
			// Update all of the attributed text formatting properties
			handler.QueryEditor?.UpdateCharacterSpacing(searchBar);
 
			// Setting any of those may have removed text alignment settings,
			// so we need to make sure those are applied, too
			handler.QueryEditor?.UpdateHorizontalTextAlignment(searchBar);
 
			// We also update MaxLength which depends on the text
			handler.PlatformView?.UpdateMaxLength(searchBar);
		}
 
		public static void MapTextColor(ISearchBarHandler handler, ISearchBar searchBar)
		{
			if (handler is SearchBarHandler platformHandler)
				handler.QueryEditor?.UpdateTextColor(searchBar);
		}
 
		public static void MapIsTextPredictionEnabled(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateIsTextPredictionEnabled(searchBar, handler?.QueryEditor);
		}
 
		public static void MapIsSpellCheckEnabled(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateIsSpellCheckEnabled(searchBar, handler?.QueryEditor);
		}
 
		public static void MapMaxLength(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateMaxLength(searchBar);
		}
 
		public static void MapIsReadOnly(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateIsReadOnly(searchBar);
		}
 
		public static void MapCancelButtonColor(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateCancelButton(searchBar);
		}
 
		internal static void MapSearchIconColor(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateSearchIcon(searchBar);
		}
		public static void MapKeyboard(ISearchBarHandler handler, ISearchBar searchBar)
		{
			handler.PlatformView?.UpdateKeyboard(searchBar);
		}
 
		void UpdateCancelButtonVisibility()
		{
			if (PlatformView.ShowsCancelButton != VirtualView.ShouldShowCancelButton())
				UpdateValue(nameof(ISearchBar.CancelButtonColor));
		}
 
		class MauiSearchBarProxy
		{
			WeakReference<SearchBarHandler>? _handler;
			WeakReference<ISearchBar>? _virtualView;
 
			ISearchBar? VirtualView => _virtualView is not null && _virtualView.TryGetTarget(out var v) ? v : null;
 
			SearchBarHandler? Handler => _handler is not null && _handler.TryGetTarget(out var h) ? h : null;
 
			public void Connect(SearchBarHandler handler, ISearchBar virtualView, MauiSearchBar platformView)
			{
				_handler = new(handler);
				_virtualView = new(virtualView);
 
				platformView.CancelButtonClicked += OnCancelClicked;
				platformView.SearchButtonClicked += OnSearchButtonClicked;
				platformView.TextSetOrChanged += OnTextPropertySet;
				platformView.OnMovedToWindow += OnMovedToWindow;
				platformView.ShouldChangeTextInRange += ShouldChangeText;
				platformView.OnEditingStarted += OnEditingStarted;
				platformView.OnEditingStopped += OnEditingStopped;
 
				if (handler.QueryEditor is UITextField editor)
					editor.EditingChanged += OnEditingChanged;
			}
 
			public void Disconnect(MauiSearchBar platformView, UITextField? editor)
			{
				_virtualView = null;
				_handler = null;
 
				platformView.CancelButtonClicked -= OnCancelClicked;
				platformView.SearchButtonClicked -= OnSearchButtonClicked;
				platformView.TextSetOrChanged -= OnTextPropertySet;
				platformView.ShouldChangeTextInRange -= ShouldChangeText;
				platformView.OnMovedToWindow -= OnMovedToWindow;
				platformView.OnEditingStarted -= OnEditingStarted;
				platformView.OnEditingStopped -= OnEditingStopped;
 
				if (editor is not null)
					editor.EditingChanged -= OnEditingChanged;
			}
 
			void OnMovedToWindow(object? sender, EventArgs e)
			{
				// The cancel button doesn't exist until the control has moved to the window
				// so we fire this off again so it can set the color
				if (Handler is SearchBarHandler handler)
				{
					handler.UpdateValue(nameof(ISearchBar.CancelButtonColor));
				}
			}
 
			void OnCancelClicked(object? sender, EventArgs args)
			{
				if (VirtualView is ISearchBar virtualView)
					virtualView.Text = string.Empty;
			}
 
			void OnSearchButtonClicked(object? sender, EventArgs e)
			{
				VirtualView?.SearchButtonPressed();
			}
 
			void OnTextPropertySet(object? sender, UISearchBarTextChangedEventArgs a)
			{
				if (VirtualView is ISearchBar virtualView)
				{
					virtualView.UpdateText(a.SearchText);
 
					if (Handler is SearchBarHandler handler)
					{
						handler.UpdateCancelButtonVisibility();
					}
				}
			}
 
			bool ShouldChangeText(UISearchBar searchBar, NSRange range, string text)
			{
				var newLength = searchBar?.Text?.Length + text.Length - range.Length;
				return newLength <= VirtualView?.MaxLength;
			}
 
			void OnEditingStarted(object? sender, EventArgs e)
			{
				if (VirtualView is ISearchBar virtualView)
					virtualView.IsFocused = true;
			}
 
			void OnEditingChanged(object? sender, EventArgs e)
			{
				if (sender is UITextField textField && VirtualView is ISearchBar virtualView)
				{
					virtualView.UpdateText(textField.Text);
				}
 
				if (Handler is SearchBarHandler handler)
				{
					handler.UpdateCancelButtonVisibility();
				}
			}
 
			void OnEditingStopped(object? sender, EventArgs e)
			{
				if (VirtualView is ISearchBar virtualView)
					virtualView.IsFocused = false;
			}
		}
	}
}