|
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform;
using ObjCRuntime;
using UIKit;
using RectangleF = CoreGraphics.CGRect;
namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS
{
[PortHandler]
internal class ReadOnlyField : NoCaretField
{
readonly HashSet<string> enableActions;
public ReadOnlyField()
{
string[] actions = { "copy:", "select:", "selectAll:" };
enableActions = new HashSet<string>(actions);
}
public override bool CanPerform(Selector action, NSObject withSender)
=> enableActions.Contains(action.Name);
}
[System.Obsolete(Compatibility.Hosting.MauiAppBuilderExtensions.UseMapperInstead)]
public class PickerRenderer : PickerRendererBase<UITextField>
{
[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
public PickerRenderer()
{
}
[PortHandler]
protected override UITextField CreateNativeControl()
{
return new ReadOnlyField { BorderStyle = UITextBorderStyle.RoundedRect };
}
}
[System.Obsolete(Compatibility.Hosting.MauiAppBuilderExtensions.UseMapperInstead)]
public abstract class PickerRendererBase<TControl> : ViewRenderer<Picker, TControl>
where TControl : UITextField
{
UIPickerView _picker;
UIColor _defaultTextColor;
bool _disposed;
bool _useLegacyColorManagement;
IElementController ElementController => Element as IElementController;
[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
public PickerRendererBase()
{
}
protected abstract override TControl CreateNativeControl();
[PortHandler("Partially ported, still missing code related to TitleColor, etc.")]
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
if (e.OldElement != null)
((INotifyCollectionChanged)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
if (e.NewElement != null)
{
if (Control == null)
{
// disabled cut, delete, and toggle actions because they can throw an unhandled native exception
var entry = CreateNativeControl();
entry.EditingDidBegin += OnStarted;
entry.EditingDidEnd += OnEnded;
entry.EditingChanged += OnEditing;
_picker = new UIPickerView();
var width = UIScreen.MainScreen.Bounds.Width;
var toolbar = new UIToolbar(new RectangleF(0, 0, width, 44)) { BarStyle = UIBarStyle.Default, Translucent = true };
var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, (o, a) =>
{
var s = (PickerSource)_picker.Model;
if (s.SelectedIndex == -1 && Element.Items != null && Element.Items.Count > 0)
UpdatePickerSelectedIndex(0);
UpdatePickerFromModel(s);
entry.ResignFirstResponder();
UpdateCharacterSpacing();
});
toolbar.SetItems(new[] { spacer, doneButton }, false);
entry.InputView = _picker;
entry.InputAccessoryView = toolbar;
entry.InputView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
entry.InputAccessoryView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
entry.InputAssistantItem.LeadingBarButtonGroups = null;
entry.InputAssistantItem.TrailingBarButtonGroups = null;
_defaultTextColor = entry.TextColor;
_useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
entry.AccessibilityTraits = UIAccessibilityTrait.Button;
SetNativeControl(entry);
}
_picker.Model = new PickerSource(this);
UpdateFont();
UpdatePicker();
UpdateTextColor();
UpdateHorizontalTextAlignment();
UpdateVerticalTextAlignment();
((INotifyCollectionChanged)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
}
base.OnElementChanged(e);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Picker.HorizontalTextAlignmentProperty.PropertyName)
UpdateHorizontalTextAlignment();
else if (e.PropertyName == Picker.VerticalTextAlignmentProperty.PropertyName)
UpdateVerticalTextAlignment();
if (e.PropertyName == Picker.TitleProperty.PropertyName || e.PropertyName == Picker.TitleColorProperty.PropertyName)
{
UpdatePicker();
}
else if (e.PropertyName == Picker.SelectedIndexProperty.PropertyName)
{
UpdatePicker();
}
else if (e.PropertyName == Picker.CharacterSpacingProperty.PropertyName)
UpdateCharacterSpacing();
else if (e.PropertyName == Picker.TextColorProperty.PropertyName || e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
UpdateTextColor();
else if (e.PropertyName == Picker.FontAttributesProperty.PropertyName || e.PropertyName == Picker.FontFamilyProperty.PropertyName ||
e.PropertyName == Picker.FontSizeProperty.PropertyName)
{
UpdateFont();
}
else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
UpdateHorizontalTextAlignment();
}
[PortHandler]
void OnEditing(object sender, EventArgs eventArgs)
{
// Reset the TextField's Text so it appears as if typing with a keyboard does not work.
var selectedIndex = Element.SelectedIndex;
var items = Element.Items;
Control.Text = selectedIndex == -1 || items == null ? "" : items[selectedIndex];
// Also clears the undo stack (undo/redo possible on iPads)
Control.UndoManager.RemoveAllActions();
}
[PortHandler]
void OnEnded(object sender, EventArgs eventArgs)
{
var s = (PickerSource)_picker.Model;
if (s.SelectedIndex != -1 && s.SelectedIndex != _picker.SelectedRowInComponent(0))
{
_picker.Select(s.SelectedIndex, 0, false);
}
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
}
[PortHandler]
void OnStarted(object sender, EventArgs eventArgs)
{
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
}
[PortHandler]
void RowsCollectionChanged(object sender, EventArgs e)
{
UpdatePicker();
}
[PortHandler("The code related to Placeholder remains to be ported")]
protected void UpdateCharacterSpacing()
{
if (Control == null)
return;
var textAttr = Control.AttributedText.WithCharacterSpacing(Element.CharacterSpacing);
if (textAttr != null)
Control.AttributedText = textAttr;
var placeHolder = Control.AttributedPlaceholder.WithCharacterSpacing(Element.CharacterSpacing);
if (placeHolder != null)
UpdateAttributedPlaceholder(placeHolder);
}
[PortHandler]
protected internal virtual void UpdateFont()
{
Control.Font = Element.ToUIFont();
}
readonly Color _defaultPlaceholderColor = ColorExtensions.PlaceholderColor.ToColor();
protected internal virtual void UpdatePlaceholder()
{
var formatted = (FormattedString)Element.Title;
if (formatted == null)
return;
var targetColor = Element.TitleColor;
if (_useLegacyColorManagement)
{
var color = targetColor == null || !Element.IsEnabled ? _defaultPlaceholderColor : targetColor;
UpdateAttributedPlaceholder(formatted.ToNSAttributedString(Element.RequireFontManager(), defaultColor: color));
}
else
{
// Using VSM color management; take whatever is in Element.PlaceholderColor
var color = targetColor ?? _defaultPlaceholderColor;
UpdateAttributedPlaceholder(formatted.ToNSAttributedString(Element.RequireFontManager(), defaultColor: color));
}
UpdateAttributedPlaceholder(Control.AttributedPlaceholder.WithCharacterSpacing(Element.CharacterSpacing));
}
protected virtual void UpdateAttributedPlaceholder(NSAttributedString nsAttributedString) =>
Control.AttributedPlaceholder = nsAttributedString;
[PortHandler]
void UpdatePicker()
{
var selectedIndex = Element.SelectedIndex;
var items = Element.Items;
UpdatePlaceholder();
var oldText = Control.Text;
Control.Text = selectedIndex == -1 || items == null || selectedIndex >= items.Count ? "" : items[selectedIndex];
UpdatePickerNativeSize(oldText);
_picker.ReloadAllComponents();
if (items == null || items.Count == 0)
return;
UpdatePickerSelectedIndex(selectedIndex);
UpdateCharacterSpacing();
}
void UpdatePickerFromModel(PickerSource s)
{
if (Element != null)
{
var oldText = Control.Text;
Control.Text = s.SelectedItem;
UpdatePickerNativeSize(oldText);
ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, s.SelectedIndex);
}
}
void UpdatePickerNativeSize(string oldText)
{
if (oldText != Control.Text)
((IVisualElementController)Element).PlatformSizeChanged();
}
[PortHandler]
void UpdatePickerSelectedIndex(int formsIndex)
{
var source = (PickerSource)_picker.Model;
source.SelectedIndex = formsIndex;
source.SelectedItem = formsIndex >= 0 ? Element.Items[formsIndex] : null;
_picker.Select(Math.Max(formsIndex, 0), 0, true);
}
[PortHandler("Partially ported, still missing FlowDirection part.")]
void UpdateHorizontalTextAlignment()
{
Control.TextAlignment = Element.HorizontalTextAlignment.ToPlatformTextAlignment(((IVisualElementController)Element).EffectiveFlowDirection);
}
void UpdateVerticalTextAlignment()
{
Control.VerticalAlignment = Element.VerticalTextAlignment.ToPlatformTextAlignment();
}
[PortHandler]
protected internal virtual void UpdateTextColor()
{
var textColor = Element.TextColor;
if (textColor == null || (!Element.IsEnabled && _useLegacyColorManagement))
Control.TextColor = _defaultTextColor;
else
Control.TextColor = textColor.ToPlatform();
// HACK This forces the color to update; there's probably a more elegant way to make this happen
Control.Text = Control.Text;
}
protected override void Dispose(bool disposing)
{
if (_disposed)
return;
_disposed = true;
if (disposing)
{
_defaultTextColor = null;
if (_picker != null)
{
if (_picker.Model != null)
{
_picker.Model.Dispose();
_picker.Model = null;
}
_picker.RemoveFromSuperview();
_picker.Dispose();
_picker = null;
}
if (Control != null)
{
Control.EditingDidBegin -= OnStarted;
Control.EditingDidEnd -= OnEnded;
Control.EditingChanged -= OnEditing;
}
if (Element != null)
((INotifyCollectionChanged)Element.Items).CollectionChanged -= RowsCollectionChanged;
}
base.Dispose(disposing);
}
[PortHandler]
class PickerSource : UIPickerViewModel
{
PickerRendererBase<TControl> _renderer;
bool _disposed;
public PickerSource(PickerRendererBase<TControl> renderer)
{
_renderer = renderer;
}
public int SelectedIndex { get; internal set; }
public string SelectedItem { get; internal set; }
public override nint GetComponentCount(UIPickerView picker)
{
return 1;
}
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
return _renderer.Element.Items != null ? _renderer.Element.Items.Count : 0;
}
public override string GetTitle(UIPickerView picker, nint row, nint component)
{
return _renderer.Element.Items[(int)row];
}
public override void Selected(UIPickerView picker, nint row, nint component)
{
if (_renderer.Element.Items.Count == 0)
{
SelectedItem = null;
SelectedIndex = -1;
}
else
{
SelectedItem = _renderer.Element.Items[(int)row];
SelectedIndex = (int)row;
}
if (_renderer.Element.On<PlatformConfiguration.iOS>().UpdateMode() == UpdateMode.Immediately)
_renderer.UpdatePickerFromModel(this);
}
protected override void Dispose(bool disposing)
{
if (_disposed)
return;
_disposed = true;
if (disposing)
_renderer = null;
base.Dispose(disposing);
}
}
}
} |