|
using System;
using System.ComponentModel;
using CoreGraphics;
using Foundation;
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;
namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS
{
[System.Obsolete(Compatibility.Hosting.MauiAppBuilderExtensions.UseMapperInstead)]
public class SearchBarRenderer : ViewRenderer<SearchBar, UISearchBar>
{
UIColor _cancelButtonTextColorDefaultDisabled;
UIColor _cancelButtonTextColorDefaultHighlighted;
UIColor _cancelButtonTextColorDefaultNormal;
UIColor _defaultTextColor;
UIColor _defaultTintColor;
UITextField _textField;
bool _textWasTyped;
string _typedText;
bool _useLegacyColorManagement;
UIToolbar _numericAccessoryView;
IElementController ElementController => Element as IElementController;
[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
public SearchBarRenderer()
{
}
[PortHandler]
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Control != null)
{
Control.CancelButtonClicked -= OnCancelClicked;
Control.SearchButtonClicked -= OnSearchButtonClicked;
Control.TextChanged -= OnTextChanged;
Control.ShouldChangeTextInRange -= ShouldChangeText;
Control.OnEditingStarted -= OnEditingEnded;
Control.OnEditingStopped -= OnEditingStarted;
}
}
base.Dispose(disposing);
}
[PortHandler]
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
if (e.NewElement != null)
{
if (Control == null)
{
var searchBar = new UISearchBar(CGRect.Empty) { ShowsCancelButton = true, BarStyle = UIBarStyle.Default };
var cancelButton = searchBar.FindDescendantView<UIButton>();
_cancelButtonTextColorDefaultNormal = cancelButton.TitleColor(UIControlState.Normal);
_cancelButtonTextColorDefaultHighlighted = cancelButton.TitleColor(UIControlState.Highlighted);
_cancelButtonTextColorDefaultDisabled = cancelButton.TitleColor(UIControlState.Disabled);
SetNativeControl(searchBar);
_textField = _textField ?? Control.FindDescendantView<UITextField>();
_useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
Control.CancelButtonClicked += OnCancelClicked;
Control.SearchButtonClicked += OnSearchButtonClicked;
Control.TextChanged += OnTextChanged;
Control.ShouldChangeTextInRange += ShouldChangeText;
Control.OnEditingStarted += OnEditingStarted;
Control.OnEditingStopped += OnEditingEnded;
}
UpdatePlaceholder();
UpdateText();
UpdateFont();
UpdateIsEnabled();
UpdateCancelButton();
UpdateHorizontalTextAlignment();
UpdateVerticalTextAlignment();
UpdateTextColor();
UpdateCharacterSpacing();
UpdateMaxLength();
UpdateKeyboard();
UpdateSearchBarStyle();
}
base.OnElementChanged(e);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == SearchBar.PlaceholderProperty.PropertyName || e.PropertyName == SearchBar.PlaceholderColorProperty.PropertyName)
UpdatePlaceholder();
else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
{
UpdateIsEnabled();
UpdateTextColor();
UpdatePlaceholder();
}
else if (e.PropertyName == SearchBar.TextColorProperty.PropertyName)
UpdateTextColor();
else if (e.IsOneOf(SearchBar.TextProperty, SearchBar.TextTransformProperty,
SearchBar.CharacterSpacingProperty))
{
UpdateText();
UpdateCharacterSpacing();
}
else if (e.PropertyName == SearchBar.CancelButtonColorProperty.PropertyName)
UpdateCancelButton();
else if (e.PropertyName == SearchBar.FontAttributesProperty.PropertyName)
UpdateFont();
else if (e.PropertyName == SearchBar.FontFamilyProperty.PropertyName)
{
UpdateFont();
}
else if (e.PropertyName == SearchBar.FontSizeProperty.PropertyName)
UpdateFont();
else if (e.PropertyName == SearchBar.HorizontalTextAlignmentProperty.PropertyName)
UpdateHorizontalTextAlignment();
else if (e.PropertyName == SearchBar.VerticalTextAlignmentProperty.PropertyName)
UpdateVerticalTextAlignment();
else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
UpdateHorizontalTextAlignment();
else if (e.PropertyName == Microsoft.Maui.Controls.InputView.MaxLengthProperty.PropertyName)
UpdateMaxLength();
else if (e.PropertyName == Microsoft.Maui.Controls.InputView.KeyboardProperty.PropertyName)
UpdateKeyboard();
else if (e.PropertyName == Microsoft.Maui.Controls.InputView.IsSpellCheckEnabledProperty.PropertyName)
UpdateKeyboard();
else if (e.PropertyName == PlatformConfiguration.iOSSpecific.SearchBar.SearchBarStyleProperty.PropertyName)
UpdateSearchBarStyle();
}
protected override void SetBackgroundColor(Color color)
{
base.SetBackgroundColor(color);
if (Control == null)
return;
if (_defaultTintColor == null)
{
_defaultTintColor = Control.BarTintColor;
}
Control.BarTintColor = color.ToPlatform(_defaultTintColor);
Control.SetBackgroundImage(new UIImage(), UIBarPosition.Any, UIBarMetrics.Default);
// updating BarTintColor resets the button color so we need to update the button color again
UpdateCancelButton();
}
[PortHandler]
protected override void SetBackground(Brush brush)
{
base.SetBackground(brush);
if (Control == null)
return;
if (brush is SolidColorBrush solidColorBrush)
Control.BarTintColor = solidColorBrush.Color.ToPlatform(_defaultTintColor);
}
public override CoreGraphics.CGSize SizeThatFits(CoreGraphics.CGSize size)
{
if (nfloat.IsInfinity(size.Width))
size.Width = (nfloat)(Element?.Parent is VisualElement parent ? parent.Width : DeviceDisplay.MainDisplayInfo.GetScaledScreenSize().Width);
var sizeThatFits = Control.SizeThatFits(size);
if (Forms.IsiOS11OrNewer)
return sizeThatFits;
////iOS10 hack because SizeThatFits always returns a width of 0
sizeThatFits.Width = (nfloat)Math.Max(sizeThatFits.Width, size.Width);
return sizeThatFits;
}
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 (Forms.IsiOS13OrNewer && previousTraitCollection?.UserInterfaceStyle != TraitCollection.UserInterfaceStyle)
UpdateTextColor();
}
void OnCancelClicked(object sender, EventArgs args)
{
ElementController.SetValueFromRenderer(SearchBar.TextProperty, null);
Control.ResignFirstResponder();
}
void OnEditingEnded(object sender, EventArgs e)
{
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
}
void OnEditingStarted(object sender, EventArgs e)
{
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
}
void OnSearchButtonClicked(object sender, EventArgs e)
{
Element?.OnSearchButtonPressed();
Control?.ResignFirstResponder();
}
void OnTextChanged(object sender, UISearchBarTextChangedEventArgs a)
{
// This only fires when text has been typed into the SearchBar; see UpdateText()
// for why this is handled in this manner.
_textWasTyped = true;
_typedText = a.SearchText;
UpdateOnTextChanged();
}
[PortHandler("The code related to Placeholder remains to be ported")]
void UpdateCharacterSpacing()
{
_textField = _textField ?? Control.FindDescendantView<UITextField>();
if (_textField == null)
return;
_textField.AttributedText = _textField.AttributedText.WithCharacterSpacing(Element.CharacterSpacing);
_textField.AttributedPlaceholder = _textField.AttributedPlaceholder.WithCharacterSpacing(Element.CharacterSpacing);
}
[PortHandler]
void UpdateHorizontalTextAlignment()
{
_textField = _textField ?? Control.FindDescendantView<UITextField>();
if (_textField == null)
return;
_textField.TextAlignment = Element.HorizontalTextAlignment.ToPlatformTextAlignment(((IVisualElementController)Element).EffectiveFlowDirection);
}
[PortHandler]
void UpdateVerticalTextAlignment()
{
_textField = _textField ?? Control.FindDescendantView<UITextField>();
if (_textField == null)
return;
_textField.VerticalAlignment = Element.VerticalTextAlignment.ToPlatformTextAlignment();
}
public virtual void UpdateCancelButton()
{
Control.ShowsCancelButton = !string.IsNullOrEmpty(Control.Text);
// We can't cache the cancel button reference because iOS drops it when it's not displayed
// and creates a brand new one when necessary, so we have to look for it each time
var cancelButton = Control.FindDescendantView<UIButton>();
if (cancelButton == null)
return;
if (Element.CancelButtonColor == null)
{
cancelButton.SetTitleColor(_cancelButtonTextColorDefaultNormal, UIControlState.Normal);
cancelButton.SetTitleColor(_cancelButtonTextColorDefaultHighlighted, UIControlState.Highlighted);
cancelButton.SetTitleColor(_cancelButtonTextColorDefaultDisabled, UIControlState.Disabled);
}
else
{
cancelButton.SetTitleColor(Element.CancelButtonColor.ToPlatform(), UIControlState.Normal);
cancelButton.SetTitleColor(Element.CancelButtonColor.ToPlatform(), UIControlState.Highlighted);
if (_useLegacyColorManagement)
{
cancelButton.SetTitleColor(_cancelButtonTextColorDefaultDisabled, UIControlState.Disabled);
}
else
{
cancelButton.SetTitleColor(Element.CancelButtonColor.ToPlatform(), UIControlState.Disabled);
}
}
}
[PortHandler]
void UpdateFont()
{
_textField = _textField ?? Control.FindDescendantView<UITextField>();
if (_textField == null)
return;
_textField.Font = Element.ToUIFont();
}
[PortHandler]
void UpdateIsEnabled()
{
Control.UserInteractionEnabled = Element.IsEnabled;
}
[PortHandler("Partially ported")]
void UpdatePlaceholder()
{
if (_textField == null)
return;
var formatted = (FormattedString)Element.Placeholder ?? string.Empty;
var targetColor = Element.PlaceholderColor;
if (_useLegacyColorManagement)
{
// Placeholder default color is 70% gray
// https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITextField_Class/index.html#//apple_ref/occ/instp/UITextField/placeholder
var color = Element.IsEnabled && targetColor != null
? targetColor : Maui.Platform.ColorExtensions.PlaceholderColor.ToColor();
_textField.AttributedPlaceholder = formatted.ToNSAttributedString(Element.RequireFontManager(), defaultColor: color);
_textField.AttributedPlaceholder.WithCharacterSpacing(Element.CharacterSpacing);
}
else
{
_textField.AttributedPlaceholder = formatted.ToNSAttributedString(Element.RequireFontManager(), defaultColor: targetColor ?? ColorExtensions.PlaceholderColor.ToColor());
_textField.AttributedPlaceholder.WithCharacterSpacing(Element.CharacterSpacing);
}
}
[PortHandler]
void UpdateText()
{
// There is at least one scenario where modifying the Element's Text value from TextChanged
// can cause issues with a Korean keyboard. The characters normally combine into larger
// characters as they are typed, but if SetValueFromRenderer is used in that manner,
// it ignores the combination and outputs them individually. This hook only fires
// when typing, so by keeping track of whether or not text was typed, we can respect
// other changes to Element.Text.
if (!_textWasTyped)
Control.Text = Element.UpdateFormsText(Element.Text, Element.TextTransform);
UpdateCancelButton();
}
void UpdateOnTextChanged()
{
ElementController?.SetValueFromRenderer(SearchBar.TextProperty, _typedText);
_textWasTyped = false;
}
void UpdateTextColor()
{
if (_textField == null)
return;
_defaultTextColor = _defaultTextColor ?? _textField.TextColor;
var targetColor = Element.TextColor;
if (_useLegacyColorManagement)
{
var color = Element.IsEnabled && targetColor != null ? targetColor : _defaultTextColor.ToColor();
_textField.TextColor = color.ToPlatform();
}
else
{
_textField.TextColor = targetColor == null ? _defaultTextColor : targetColor.ToPlatform();
}
}
void UpdateMaxLength()
{
var currentControlText = Control.Text;
if (currentControlText.Length > Element.MaxLength)
Control.Text = currentControlText.Substring(0, Element.MaxLength);
}
bool ShouldChangeText(UISearchBar searchBar, NSRange range, string text)
{
var newLength = searchBar?.Text?.Length + text.Length - range.Length;
return newLength <= Element?.MaxLength;
}
[PortHandler]
void UpdateKeyboard()
{
var keyboard = Element.Keyboard;
Control.ApplyKeyboard(keyboard);
if (!(keyboard is CustomKeyboard))
{
if (Element.IsSet(Microsoft.Maui.Controls.InputView.IsSpellCheckEnabledProperty))
{
if (!Element.IsSpellCheckEnabled)
{
Control.SpellCheckingType = UITextSpellCheckingType.No;
}
}
}
// iPhone does not have an enter key on numeric keyboards
if (DeviceInfo.Idiom == DeviceIdiom.Phone && (keyboard == Keyboard.Numeric || keyboard == Keyboard.Telephone))
{
_numericAccessoryView = _numericAccessoryView ?? CreateNumericKeyboardAccessoryView();
Control.InputAccessoryView = _numericAccessoryView;
}
else
{
Control.InputAccessoryView = null;
}
Control.ReloadInputViews();
}
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;
}
[PortHandler]
void UpdateSearchBarStyle()
{
Control.SearchBarStyle = Element.OnThisPlatform().GetSearchBarStyle().ToPlatformSearchBarStyle();
}
}
}
|