File: Compatibility\Handlers\Shell\iOS\SearchHandlerAppearanceTracker.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform;
using ObjCRuntime;
using UIKit;
 
namespace Microsoft.Maui.Controls.Platform.Compatibility
{
	public class SearchHandlerAppearanceTracker : IDisposable
	{
		IFontManager _fontManager;
		UIColor _cancelButtonTextColorDefaultDisabled;
		UIColor _cancelButtonTextColorDefaultHighlighted;
		UIColor _cancelButtonTextColorDefaultNormal;
		UIColor _defaultTextColor;
		UIColor _defaultTintColor;
		UIColor _defaultClearIconTintColor;
		UIColor _defaultPlaceholderTintColor;
		bool _hasCustomBackground;
		UIColor _defaultBackgroundColor;
		SearchHandler _searchHandler;
		UISearchBar _uiSearchBar;
		UIToolbar _numericAccessoryView;
		bool _disposed;
 
		public SearchHandlerAppearanceTracker(UISearchBar searchBar, SearchHandler searchHandler, IFontManager fontManager)
		{
			_fontManager = fontManager;
			_searchHandler = searchHandler;
			_searchHandler.PropertyChanged += SearchHandlerPropertyChanged;
			_searchHandler.FocusChangeRequested += SearchHandlerFocusChangeRequested;
			_uiSearchBar = searchBar;
			_uiSearchBar.OnEditingStarted += OnEditingStarted;
			_uiSearchBar.OnEditingStopped += OnEditingEnded;
			_uiSearchBar.TextChanged += OnTextChanged;
			_uiSearchBar.ShowsCancelButton = false;
			GetDefaultSearchBarColors(_uiSearchBar);
			var uiTextField = searchBar.FindDescendantView<UITextField>();
			UpdateSearchBarColors();
			UpdateSearchBarHorizontalTextAlignment(uiTextField);
			UpdateSearchBarVerticalTextAlignment(uiTextField);
			UpdateFont(uiTextField);
			UpdateKeyboard();
		}
 
		public void UpdateSearchBarColors()
		{
			var cancelButton = _uiSearchBar.FindDescendantView<UIButton>();
			var uiTextField = _uiSearchBar.FindDescendantView<UITextField>();
 
			UpdateTextColor(uiTextField);
			UpdateSearchBarPlaceholder(uiTextField);
			UpdateCancelButtonColor(cancelButton);
			UpdateSearchBarBackgroundColor(uiTextField);
			UpdateTextTransform(uiTextField);
		}
 
		internal void UpdateFlowDirection(Shell shell)
		{
			_uiSearchBar.UpdateFlowDirection(shell);
 
			// This UIToolbar variable is only initialized in case the platform is a Phone.
			_numericAccessoryView?.UpdateFlowDirection(shell);
 
			var uiTextField = _uiSearchBar.FindDescendantView<UITextField>();
			UpdateSearchBarHorizontalTextAlignment(uiTextField, shell);
		}
 
		void SearchHandlerFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e)
		{
			if (e.Focus)
				e.Result = _uiSearchBar.BecomeFirstResponder();
		}
 
		void SearchHandlerPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			if (e.Is(SearchHandler.BackgroundColorProperty))
			{
				UpdateSearchBarBackgroundColor(_uiSearchBar.FindDescendantView<UITextField>());
			}
			else if (e.Is(SearchHandler.TextColorProperty))
			{
				UpdateTextColor(_uiSearchBar.FindDescendantView<UITextField>());
			}
			else if (e.Is(SearchHandler.TextTransformProperty))
			{
				UpdateTextTransform(_uiSearchBar.FindDescendantView<UITextField>());
			}
			else if (e.IsOneOf(SearchHandler.PlaceholderColorProperty, SearchHandler.PlaceholderProperty))
			{
				UpdateSearchBarPlaceholder(_uiSearchBar.FindDescendantView<UITextField>());
			}
			else if (e.IsOneOf(SearchHandler.FontFamilyProperty, SearchHandler.FontAttributesProperty, SearchHandler.FontSizeProperty))
			{
				UpdateFont(_uiSearchBar.FindDescendantView<UITextField>());
			}
			else if (e.Is(SearchHandler.CancelButtonColorProperty))
			{
				UpdateCancelButtonColor(_uiSearchBar.FindDescendantView<UIButton>());
			}
			else if (e.Is(SearchHandler.KeyboardProperty))
			{
				UpdateKeyboard();
			}
			else if (e.Is(SearchHandler.HorizontalTextAlignmentProperty))
			{
				UpdateSearchBarHorizontalTextAlignment(_uiSearchBar.FindDescendantView<UITextField>());
			}
			else if (e.Is(SearchHandler.VerticalTextAlignmentProperty))
			{
				UpdateSearchBarVerticalTextAlignment(_uiSearchBar.FindDescendantView<UITextField>());
			}
		}
 
		void GetDefaultSearchBarColors(UISearchBar searchBar)
		{
			_defaultTintColor = searchBar.BarTintColor;
 
			var cancelButton = searchBar.FindDescendantView<UIButton>();
			if (cancelButton != null)
			{
				_cancelButtonTextColorDefaultNormal = cancelButton.TitleColor(UIControlState.Normal);
				_cancelButtonTextColorDefaultHighlighted = cancelButton.TitleColor(UIControlState.Highlighted);
				_cancelButtonTextColorDefaultDisabled = cancelButton.TitleColor(UIControlState.Disabled);
			}
		}
 
		void UpdateFont(UITextField textField)
		{
			if (textField == null)
				return;
 
 
			textField.Font = _searchHandler.ToFont().ToUIFont(_fontManager);
		}
 
		void UpdateSearchBarBackgroundColor(UITextField textField)
		{
			if (textField == null)
				return;
 
			var backGroundColor = _searchHandler.BackgroundColor;
 
			if (!_hasCustomBackground && backGroundColor == null)
				return;
 
			var backgroundView = textField.Subviews[0];
 
			if (backGroundColor == null)
			{
				backgroundView.Layer.CornerRadius = 0;
				backgroundView.ClipsToBounds = false;
				backgroundView.BackgroundColor = _defaultBackgroundColor;
			}
 
			_hasCustomBackground = true;
 
			backgroundView.Layer.CornerRadius = 10;
			backgroundView.ClipsToBounds = true;
			if (_defaultBackgroundColor == null)
				_defaultBackgroundColor = backgroundView.BackgroundColor;
			backgroundView.BackgroundColor = backGroundColor.ToPlatform();
		}
 
		void UpdateCancelButtonColor(UIButton cancelButton)
		{
			if (cancelButton == null)
				return;
 
			var cancelColor = _searchHandler.CancelButtonColor;
			if (cancelColor == null)
			{
				cancelButton.SetTitleColor(_cancelButtonTextColorDefaultNormal, UIControlState.Normal);
				cancelButton.SetTitleColor(_cancelButtonTextColorDefaultHighlighted, UIControlState.Highlighted);
				cancelButton.SetTitleColor(_cancelButtonTextColorDefaultDisabled, UIControlState.Disabled);
			}
			else
			{
				var cancelUIColor = cancelColor.ToPlatform();
				cancelButton.SetTitleColor(cancelUIColor, UIControlState.Normal);
				cancelButton.SetTitleColor(cancelUIColor, UIControlState.Highlighted);
				cancelButton.SetTitleColor(cancelUIColor, UIControlState.Disabled);
			}
 
			UpdateClearIconColor(cancelColor);
		}
 
		void UpdateSearchBarPlaceholder(UITextField textField)
		{
			if (textField == null)
				return;
 
			var formatted = (FormattedString)_searchHandler.Placeholder ?? string.Empty;
			var targetColor = _searchHandler.PlaceholderColor;
			var placeHolderColor = targetColor ?? Microsoft.Maui.Platform.ColorExtensions.PlaceholderColor.ToColor();
			textField.AttributedPlaceholder = formatted.ToNSAttributedString(_fontManager, defaultHorizontalAlignment: _searchHandler.HorizontalTextAlignment, defaultColor: placeHolderColor);
 
			//Center placeholder
			//var width = (_uiSearchBar.Frame.Width / 2) - textField.AttributedPlaceholder.Size.Width;
 
			//var paddingView = new UIImageView(new CGRect(0, 0, width, _uiSearchBar.Frame.Height));
 
			//textField.LeftView = paddingView;
			//var paddingView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: searchBar.frame.height))
 
			//searchBarTextField.leftView = paddingView
 
			//searchBarTextField.leftViewMode = .unlessEditing
		}
 
		void UpdateTextTransform(UITextField textField)
		{
			if (textField == null)
				return;
 
			textField.Text = _searchHandler.UpdateFormsText(textField.Text, _searchHandler.TextTransform);
		}
 
		void UpdateTextColor(UITextField textField)
		{
			if (textField == null)
				return;
 
			_defaultTextColor = _defaultTextColor ?? textField.TextColor;
			var targetColor = _searchHandler.TextColor;
 
			textField.TextColor = targetColor?.ToPlatform() ?? _defaultTextColor;
			UpdateSearchBarTintColor(targetColor);
			UpdateSearchButtonIconColor(targetColor);
			UpdateClearPlaceholderIconColor(targetColor);
		}
 
		void UpdateSearchBarTintColor(Color targetColor)
		{
			_uiSearchBar.TintColor = targetColor?.ToPlatform() ?? _defaultTintColor;
		}
 
		void UpdateSearchButtonIconColor(Color targetColor)
		{
			var imageView = _uiSearchBar.FindDescendantView<UITextField>()?.LeftView as UIImageView;
 
			_defaultClearIconTintColor = _defaultClearIconTintColor ?? imageView.TintColor;
 
			SetSearchBarIconColor(imageView, targetColor, _defaultClearIconTintColor);
		}
 
		void UpdateClearPlaceholderIconColor(Color targetColor)
		{
			var uiTextField = _uiSearchBar.FindDescendantView<UITextField>();
 
			var uiButton = uiTextField.FindDescendantView<UIButton>();
 
			if (uiButton == null)
				return;
 
			_defaultPlaceholderTintColor = _defaultPlaceholderTintColor ?? uiButton?.ImageView?.TintColor;
 
 
			SetSearchBarIconColor(uiButton, targetColor, _defaultPlaceholderTintColor);
			uiButton.TintColor = targetColor?.ToPlatform() ?? _defaultPlaceholderTintColor;
		}
 
		void UpdateClearIconColor(Color targetColor)
		{
			var uiTextField = _uiSearchBar.FindDescendantView<UITextField>();
 
			var uiButton = uiTextField.ValueForKey(new NSString("clearButton")) as UIButton;
 
			if (uiButton == null)
				return;
 
			_defaultClearIconTintColor = _defaultClearIconTintColor ?? uiButton?.TintColor;
 
			SetSearchBarIconColor(uiButton, targetColor, _defaultClearIconTintColor);
		}
 
		void UpdateSearchBarHorizontalTextAlignment(UITextField textField, IView view = null)
		{
			if (textField == null)
				return;
 
			textField.TextAlignment = _searchHandler.HorizontalTextAlignment.ToPlatformHorizontal(textField.EffectiveUserInterfaceLayoutDirection);
		}
 
		void UpdateSearchBarVerticalTextAlignment(UITextField textField)
		{
			if (textField == null)
				return;
 
			textField.VerticalAlignment = _searchHandler.VerticalTextAlignment.ToPlatformVertical();
		}
 
		void UpdateKeyboard()
		{
			var keyboard = _searchHandler.Keyboard;
			_uiSearchBar.ApplyKeyboard(keyboard);
 
			// iPhone does not have an enter key on numeric keyboards
			if (DeviceInfo.Idiom == DeviceIdiom.Phone && (keyboard == Keyboard.Numeric || keyboard == Keyboard.Telephone))
			{
				_numericAccessoryView ??= CreateNumericKeyboardAccessoryView();
				_uiSearchBar.InputAccessoryView = _numericAccessoryView;
			}
			else
			{
				_uiSearchBar.InputAccessoryView = null;
			}
 
			_uiSearchBar.ReloadInputViews();
		}
 
		void OnEditingEnded(object sender, EventArgs e)
		{
			_searchHandler.SetIsFocused(false);
		}
 
		void OnEditingStarted(object sender, EventArgs e)
		{
			UpdateCancelButtonColor(_uiSearchBar.FindDescendantView<UIButton>());
			_searchHandler.SetIsFocused(true);
			//ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
		}
 
		void OnTextChanged(object sender, UISearchBarTextChangedEventArgs e)
		{
			UpdateCancelButtonColor(_uiSearchBar.FindDescendantView<UIButton>());
		}
 
 
		void OnSearchButtonClicked(object sender, EventArgs e)
		{
			((ISearchHandlerController)_searchHandler).QueryConfirmed();
			_uiSearchBar.ResignFirstResponder();
		}
 
		UIToolbar CreateNumericKeyboardAccessoryView()
		{
			var keyboardWidth = UIScreen.MainScreen.Bounds.Width;
			var accessoryView = new UIToolbar(new CGRect(0, 0, keyboardWidth, 44)) { BarStyle = UIBarStyle.Default, Translucent = true };
 
			var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
			var searchButton = new UIBarButtonItem(UIBarButtonSystemItem.Search, OnSearchButtonClicked);
			accessoryView.SetItems(new[] { spacer, searchButton }, false);
 
			return accessoryView;
		}
 
		static void SetSearchBarIconColor(UIImageView imageView, Color targetColor, UIColor defaultTintColor)
		{
			var icon = imageView?.Image;
 
			if (icon == null || (targetColor == null && defaultTintColor == null))
				return;
 
			var newIcon = icon.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
			imageView.TintColor = targetColor?.ToPlatform() ?? defaultTintColor;
			imageView.Image = newIcon;
		}
 
		static void SetSearchBarIconColor(UIButton button, Color targetColor, UIColor defaultTintColor)
		{
			var icon = button.ImageView?.Image;
 
			if (icon == null || (targetColor == null && defaultTintColor == null))
				return;
 
			var newIcon = icon.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
			button.SetImage(newIcon, UIControlState.Normal);
			button.SetImage(newIcon, UIControlState.Selected);
			button.SetImage(newIcon, UIControlState.Highlighted);
			button.TintColor = button.ImageView.TintColor = targetColor?.ToPlatform() ?? defaultTintColor;
		}
 
		protected virtual void Dispose(bool disposing)
		{
			if (_disposed)
				return;
 
			_disposed = true;
 
			if (disposing)
			{
				if (_uiSearchBar != null)
				{
					_uiSearchBar.OnEditingStarted -= OnEditingStarted;
					_uiSearchBar.OnEditingStopped -= OnEditingEnded;
					_uiSearchBar.TextChanged -= OnTextChanged;
				}
				if (_searchHandler != null)
				{
					_searchHandler.FocusChangeRequested -= SearchHandlerFocusChangeRequested;
					_searchHandler.PropertyChanged -= SearchHandlerPropertyChanged;
				}
				_searchHandler = null;
				_uiSearchBar = null;
			}
		}
 
		public void Dispose()
		{
			Dispose(true);
		}
	}
}