maui-linux/Xamarin.Forms.Platform.iOS/Renderers/EntryRenderer.cs

478 строки
14 KiB
C#

using System;
using System.ComponentModel;
using System.Drawing;
using CoreGraphics;
using Foundation;
using UIKit;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
using Specifics = Xamarin.Forms.PlatformConfiguration.iOSSpecific.Entry;
namespace Xamarin.Forms.Platform.iOS
{
public class EntryRenderer : ViewRenderer<Entry, UITextField>
{
UIColor _defaultTextColor;
// 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
readonly Color _defaultPlaceholderColor = ColorExtensions.SeventyPercentGrey.ToColor();
UIColor _defaultCursorColor;
bool _useLegacyColorManagement;
bool _disposed;
IDisposable _selectedTextRangeObserver;
bool _nativeSelectionIsUpdating;
bool _cursorPositionChangePending;
bool _selectionLengthChangePending;
static readonly int baseHeight = 30;
static CGSize initialSize = CGSize.Empty;
public EntryRenderer()
{
Frame = new RectangleF(0, 20, 320, 40);
}
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
var baseResult = base.GetDesiredSize(widthConstraint, heightConstraint);
if (Forms.IsiOS11OrNewer)
return baseResult;
NSString testString = new NSString("Tj");
var testSize = testString.GetSizeUsingAttributes(new UIStringAttributes { Font = Control.Font });
double height = baseHeight + testSize.Height - initialSize.Height;
height = Math.Round(height);
return new SizeRequest(new Size(baseResult.Request.Width, height));
}
IElementController ElementController => Element as IElementController;
protected override void Dispose(bool disposing)
{
if (_disposed)
return;
_disposed = true;
if (disposing)
{
_defaultTextColor = null;
if (Control != null)
{
_defaultCursorColor = Control.TintColor;
Control.EditingDidBegin -= OnEditingBegan;
Control.EditingChanged -= OnEditingChanged;
Control.EditingDidEnd -= OnEditingEnded;
Control.ShouldChangeCharacters -= ShouldChangeCharacters;
_selectedTextRangeObserver?.Dispose();
}
}
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
return;
if (Control == null)
{
var textField = new UITextField(RectangleF.Empty);
SetNativeControl(textField);
// Cache the default text color
_defaultTextColor = textField.TextColor;
_useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
textField.BorderStyle = UITextBorderStyle.RoundedRect;
textField.ClipsToBounds = true;
textField.EditingChanged += OnEditingChanged;
textField.ShouldReturn = OnShouldReturn;
textField.EditingDidBegin += OnEditingBegan;
textField.EditingDidEnd += OnEditingEnded;
textField.ShouldChangeCharacters += ShouldChangeCharacters;
_selectedTextRangeObserver = textField.AddObserver("selectedTextRange", NSKeyValueObservingOptions.New, UpdateCursorFromControl);
}
// When we set the control text, it triggers the UpdateCursorFromControl event, which updates CursorPosition and SelectionLength;
// These one-time-use variables will let us initialize a CursorPosition and SelectionLength via ctor/xaml/etc.
_cursorPositionChangePending = Element.IsSet(Entry.CursorPositionProperty);
_selectionLengthChangePending = Element.IsSet(Entry.SelectionLengthProperty);
UpdatePlaceholder();
UpdatePassword();
UpdateText();
UpdateColor();
UpdateFont();
UpdateKeyboard();
UpdateAlignment();
UpdateAdjustsFontSizeToFitWidth();
UpdateMaxLength();
UpdateReturnType();
if (_cursorPositionChangePending || _selectionLengthChangePending)
UpdateCursorSelection();
UpdateCursorColor();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == Entry.PlaceholderProperty.PropertyName || e.PropertyName == Entry.PlaceholderColorProperty.PropertyName)
UpdatePlaceholder();
else if (e.PropertyName == Entry.IsPasswordProperty.PropertyName)
UpdatePassword();
else if (e.PropertyName == Entry.TextProperty.PropertyName)
UpdateText();
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
UpdateColor();
else if (e.PropertyName == Xamarin.Forms.InputView.KeyboardProperty.PropertyName)
UpdateKeyboard();
else if (e.PropertyName == Xamarin.Forms.InputView.IsSpellCheckEnabledProperty.PropertyName)
UpdateKeyboard();
else if (e.PropertyName == Entry.IsTextPredictionEnabledProperty.PropertyName)
UpdateKeyboard();
else if (e.PropertyName == Entry.HorizontalTextAlignmentProperty.PropertyName)
UpdateAlignment();
else if (e.PropertyName == Entry.FontAttributesProperty.PropertyName)
UpdateFont();
else if (e.PropertyName == Entry.FontFamilyProperty.PropertyName)
UpdateFont();
else if (e.PropertyName == Entry.FontSizeProperty.PropertyName)
UpdateFont();
else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
{
UpdateColor();
UpdatePlaceholder();
}
else if (e.PropertyName == Specifics.AdjustsFontSizeToFitWidthProperty.PropertyName)
UpdateAdjustsFontSizeToFitWidth();
else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
UpdateAlignment();
else if (e.PropertyName == Xamarin.Forms.InputView.MaxLengthProperty.PropertyName)
UpdateMaxLength();
else if (e.PropertyName == Entry.ReturnTypeProperty.PropertyName)
UpdateReturnType();
else if (e.PropertyName == Entry.CursorPositionProperty.PropertyName)
UpdateCursorSelection();
else if (e.PropertyName == Entry.SelectionLengthProperty.PropertyName)
UpdateCursorSelection();
else if (e.PropertyName == Specifics.CursorColorProperty.PropertyName)
UpdateCursorColor();
base.OnElementPropertyChanged(sender, e);
}
void OnEditingBegan(object sender, EventArgs e)
{
if (!_cursorPositionChangePending && !_selectionLengthChangePending)
UpdateCursorFromControl(null);
else
UpdateCursorSelection();
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
}
void OnEditingChanged(object sender, EventArgs eventArgs)
{
ElementController.SetValueFromRenderer(Entry.TextProperty, Control.Text);
UpdateCursorFromControl(null);
}
void OnEditingEnded(object sender, EventArgs e)
{
// Typing aid changes don't always raise EditingChanged event
if (Control.Text != Element.Text)
{
ElementController.SetValueFromRenderer(Entry.TextProperty, Control.Text);
}
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
}
protected virtual bool OnShouldReturn(UITextField view)
{
Control.ResignFirstResponder();
((IEntryController)Element).SendCompleted();
return false;
}
void UpdateAlignment()
{
Control.TextAlignment = Element.HorizontalTextAlignment.ToNativeTextAlignment(((IVisualElementController)Element).EffectiveFlowDirection);
}
void UpdateColor()
{
var textColor = Element.TextColor;
if (_useLegacyColorManagement)
{
Control.TextColor = textColor.IsDefault || !Element.IsEnabled ? _defaultTextColor : textColor.ToUIColor();
}
else
{
Control.TextColor = textColor.IsDefault ? _defaultTextColor : textColor.ToUIColor();
}
}
void UpdateAdjustsFontSizeToFitWidth()
{
Control.AdjustsFontSizeToFitWidth = Element.OnThisPlatform().AdjustsFontSizeToFitWidth();
}
void UpdateFont()
{
if (initialSize == CGSize.Empty)
{
NSString testString = new NSString("Tj");
initialSize = testString.StringSize(Control.Font);
}
Control.Font = Element.ToUIFont();
}
void UpdateKeyboard()
{
var keyboard = Element.Keyboard;
Control.ApplyKeyboard(keyboard);
if (!(keyboard is Internals.CustomKeyboard))
{
if (Element.IsSet(Xamarin.Forms.InputView.IsSpellCheckEnabledProperty))
{
if (!Element.IsSpellCheckEnabled)
{
Control.SpellCheckingType = UITextSpellCheckingType.No;
}
}
if (Element.IsSet(Xamarin.Forms.Entry.IsTextPredictionEnabledProperty))
{
if (!Element.IsTextPredictionEnabled)
{
Control.AutocorrectionType = UITextAutocorrectionType.No;
}
}
}
Control.ReloadInputViews();
}
void UpdatePassword()
{
if (Element.IsPassword && Control.IsFirstResponder)
{
Control.Enabled = false;
Control.SecureTextEntry = true;
Control.Enabled = Element.IsEnabled;
Control.BecomeFirstResponder();
}
else
Control.SecureTextEntry = Element.IsPassword;
}
void UpdatePlaceholder()
{
var formatted = (FormattedString)Element.Placeholder;
if (formatted == null)
return;
var targetColor = Element.PlaceholderColor;
if (_useLegacyColorManagement)
{
var color = targetColor.IsDefault || !Element.IsEnabled ? _defaultPlaceholderColor : targetColor;
Control.AttributedPlaceholder = formatted.ToAttributed(Element, color);
}
else
{
// Using VSM color management; take whatever is in Element.PlaceholderColor
var color = targetColor.IsDefault ? _defaultPlaceholderColor : targetColor;
Control.AttributedPlaceholder = formatted.ToAttributed(Element, color);
}
}
void UpdateText()
{
// ReSharper disable once RedundantCheckBeforeAssignment
if (Control.Text != Element.Text)
Control.Text = Element.Text;
}
void UpdateMaxLength()
{
var currentControlText = Control.Text;
if (currentControlText.Length > Element.MaxLength)
Control.Text = currentControlText.Substring(0, Element.MaxLength);
}
bool ShouldChangeCharacters(UITextField textField, NSRange range, string replacementString)
{
var newLength = textField?.Text?.Length + replacementString.Length - range.Length;
return newLength <= Element?.MaxLength;
}
void UpdateReturnType()
{
if (Control == null || Element == null)
return;
Control.ReturnKeyType = Element.ReturnType.ToUIReturnKeyType();
}
void UpdateCursorFromControl(NSObservedChange obj)
{
if (_nativeSelectionIsUpdating || Control == null || Element == null)
return;
var currentSelection = Control.SelectedTextRange;
if (currentSelection != null)
{
if (!_cursorPositionChangePending)
{
int newCursorPosition = (int)Control.GetOffsetFromPosition(Control.BeginningOfDocument, currentSelection.Start);
if (newCursorPosition != Element.CursorPosition)
SetCursorPositionFromRenderer(newCursorPosition);
}
if (!_selectionLengthChangePending)
{
int selectionLength = (int)Control.GetOffsetFromPosition(currentSelection.Start, currentSelection.End);
if (selectionLength != Element.SelectionLength)
SetSelectionLengthFromRenderer(selectionLength);
}
}
}
void UpdateCursorSelection()
{
if (_nativeSelectionIsUpdating || Control == null || Element == null)
return;
_cursorPositionChangePending = _selectionLengthChangePending = true;
// If this is run from the ctor, the control is likely too early in its lifecycle to be first responder yet.
// Anything done here will have no effect, so we'll skip this work until later.
// We'll try again when the control does become first responder later OnEditingBegan
if (Control.BecomeFirstResponder())
{
try
{
int cursorPosition = Element.CursorPosition;
UITextPosition start = GetSelectionStart(cursorPosition, out int startOffset);
UITextPosition end = GetSelectionEnd(cursorPosition, start, startOffset);
Control.SelectedTextRange = Control.GetTextRange(start, end);
}
catch (Exception ex)
{
Internals.Log.Warning("Entry", $"Failed to set Control.SelectedTextRange from CursorPosition/SelectionLength: {ex}");
}
finally
{
_cursorPositionChangePending = _selectionLengthChangePending = false;
}
}
}
UITextPosition GetSelectionEnd(int cursorPosition, UITextPosition start, int startOffset)
{
UITextPosition end = start;
int endOffset = startOffset;
int selectionLength = Element.SelectionLength;
if (Element.IsSet(Entry.SelectionLengthProperty))
{
end = Control.GetPosition(start, Math.Max(startOffset, Math.Min(Control.Text.Length - cursorPosition, selectionLength))) ?? start;
endOffset = Math.Max(startOffset, (int)Control.GetOffsetFromPosition(Control.BeginningOfDocument, end));
}
int newSelectionLength = Math.Max(0, endOffset - startOffset);
if (newSelectionLength != selectionLength)
SetSelectionLengthFromRenderer(newSelectionLength);
return end;
}
UITextPosition GetSelectionStart(int cursorPosition, out int startOffset)
{
UITextPosition start = Control.EndOfDocument;
startOffset = Control.Text.Length;
if (Element.IsSet(Entry.CursorPositionProperty))
{
start = Control.GetPosition(Control.BeginningOfDocument, cursorPosition) ?? Control.EndOfDocument;
startOffset = Math.Max(0, (int)Control.GetOffsetFromPosition(Control.BeginningOfDocument, start));
}
if (startOffset != cursorPosition)
SetCursorPositionFromRenderer(startOffset);
return start;
}
void UpdateCursorColor()
{
var control = Control;
if (control == null || Element == null)
return;
if (Element.IsSet(Specifics.CursorColorProperty))
{
var color = Element.OnThisPlatform().GetCursorColor();
if (color == Color.Default)
control.TintColor = _defaultCursorColor;
else
control.TintColor = color.ToUIColor();
}
}
void SetCursorPositionFromRenderer(int start)
{
try
{
_nativeSelectionIsUpdating = true;
ElementController?.SetValueFromRenderer(Entry.CursorPositionProperty, start);
}
catch (Exception ex)
{
Internals.Log.Warning("Entry", $"Failed to set CursorPosition from renderer: {ex}");
}
finally
{
_nativeSelectionIsUpdating = false;
}
}
void SetSelectionLengthFromRenderer(int selectionLength)
{
try
{
_nativeSelectionIsUpdating = true;
ElementController?.SetValueFromRenderer(Entry.SelectionLengthProperty, selectionLength);
}
catch (Exception ex)
{
Internals.Log.Warning("Entry", $"Failed to set SelectionLength from renderer: {ex}");
}
finally
{
_nativeSelectionIsUpdating = false;
}
}
}
}