|
using System;
using System.Collections.Specialized;
using UIKit;
using RectangleF = CoreGraphics.CGRect;
namespace Microsoft.Maui.Handlers
{
public partial class PickerHandler : ViewHandler<IPicker, MauiPicker>
{
readonly MauiPickerProxy _proxy = new();
UIPickerView? _pickerView;
#if !MACCATALYST
protected override MauiPicker CreatePlatformView()
{
_pickerView = new UIPickerView();
var platformPicker = new MauiPicker(_pickerView)
{
BorderStyle = UITextBorderStyle.RoundedRect,
InputView = _pickerView,
InputAccessoryView = new MauiDoneAccessoryView(_proxy.OnDone),
};
platformPicker.InputView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
platformPicker.InputAccessoryView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
platformPicker.InputAssistantItem.LeadingBarButtonGroups = null;
platformPicker.InputAssistantItem.TrailingBarButtonGroups = null;
platformPicker.AccessibilityTraits = UIAccessibilityTrait.Button;
_pickerView.Model = new PickerSource(this);
return platformPicker;
}
void OnDone() => FinishSelectItem(_pickerView, PlatformView);
#else
protected override MauiPicker CreatePlatformView() =>
new MauiPicker(null) { BorderStyle = UITextBorderStyle.RoundedRect };
void DisplayAlert(MauiPicker uITextField)
{
var paddingTitle = 0;
if (!string.IsNullOrEmpty(VirtualView.Title))
paddingTitle += 25;
var pickerHeight = 240;
var frame = new RectangleF(0, paddingTitle, 269, pickerHeight);
var pickerView = new UIPickerView(frame);
pickerView.Model = new PickerSource(this);
pickerView?.ReloadAllComponents();
var pickerController = UIAlertController.Create(VirtualView.Title, "", UIAlertControllerStyle.ActionSheet);
// needs translation
pickerController.AddAction(UIAlertAction.Create("Done",
UIAlertActionStyle.Default,
action => FinishSelectItem(pickerView, uITextField)
));
if (pickerController.View != null && pickerView != null)
{
pickerController.View.AddSubview(pickerView);
var doneButtonHeight = 90;
var height = NSLayoutConstraint.Create(pickerController.View, NSLayoutAttribute.Height, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1, pickerHeight + doneButtonHeight);
pickerController.View.AddConstraint(height);
}
var popoverPresentation = pickerController.PopoverPresentationController;
if (popoverPresentation != null)
{
popoverPresentation.SourceView = uITextField;
popoverPresentation.SourceRect = uITextField.Bounds;
}
var platformWindow = MauiContext?.GetPlatformWindow();
platformWindow?.BeginInvokeOnMainThread(() =>
{
_ = platformWindow?.RootViewController?.PresentViewControllerAsync(pickerController, true);
});
}
#endif
internal bool UpdateImmediately { get; set; }
protected override void ConnectHandler(MauiPicker platformView)
{
_proxy.Connect(this, VirtualView, platformView);
base.ConnectHandler(platformView);
}
protected override void DisconnectHandler(MauiPicker platformView)
{
_proxy.Disconnect(platformView);
if (_pickerView != null)
{
if (_pickerView.Model != null)
{
_pickerView.Model = null;
}
_pickerView.RemoveFromSuperview();
_pickerView = null;
}
base.DisconnectHandler(platformView);
}
static void Reload(IPickerHandler handler)
{
handler.PlatformView.UpdatePicker(handler.VirtualView);
}
// Uncomment me on NET8 [Obsolete]
public static void MapReload(IPickerHandler handler, IPicker picker, object? args) => Reload(handler);
internal static void MapItems(IPickerHandler handler, IPicker picker) => Reload(handler);
public static void MapTitle(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateTitle(picker);
}
public static void MapTitleColor(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateTitleColor(picker);
}
public static void MapSelectedIndex(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateSelectedIndex(picker);
}
public static void MapCharacterSpacing(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateCharacterSpacing(picker);
}
public static void MapFont(IPickerHandler handler, IPicker picker)
{
var fontManager = handler.GetRequiredService<IFontManager>();
handler.PlatformView?.UpdateFont(picker, fontManager);
}
public static void MapHorizontalTextAlignment(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateHorizontalTextAlignment(picker);
}
public static void MapTextColor(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateTextColor(picker);
}
public static void MapVerticalTextAlignment(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateVerticalTextAlignment(picker);
}
void UpdatePickerFromPickerSource(PickerSource? pickerSource)
{
if (VirtualView == null || PlatformView == null || pickerSource == null)
return;
PlatformView.Text = VirtualView.GetItem(pickerSource.SelectedIndex);
VirtualView.SelectedIndex = pickerSource.SelectedIndex;
}
void UpdatePickerSelectedIndex(UIPickerView? pickerView, int formsIndex)
{
if (VirtualView == null || pickerView == null)
return;
var source = (PickerSource)pickerView.Model;
source.SelectedIndex = formsIndex;
pickerView.Select(Math.Max(formsIndex, 0), 0, true);
}
void FinishSelectItem(UIPickerView? pickerView, MauiPicker textField)
{
var pickerSource = pickerView?.Model as PickerSource;
var count = VirtualView?.GetCount() ?? 0;
if (pickerSource != null && pickerSource.SelectedIndex == -1 && count > 0)
UpdatePickerSelectedIndex(pickerView, 0);
UpdatePickerFromPickerSource(pickerSource);
textField.ResignFirstResponder();
}
class MauiPickerProxy
{
WeakReference<PickerHandler>? _handler;
WeakReference<IPicker>? _virtualView;
IPicker? VirtualView => _virtualView is not null && _virtualView.TryGetTarget(out var v) ? v : null;
PickerHandler? Handler => _handler is not null && _handler.TryGetTarget(out var h) ? h : null;
public void Connect(PickerHandler handler, IPicker virtualView, MauiPicker platformView)
{
_handler = new(handler);
_virtualView = new(virtualView);
#if MACCATALYST
platformView.ShouldBeginEditing += OnShouldBeginEditing;
#endif
platformView.EditingDidBegin += OnStarted;
platformView.EditingDidEnd += OnEnded;
platformView.EditingChanged += OnEditing;
}
public void Disconnect(MauiPicker platformView)
{
#if MACCATALYST
platformView.ShouldBeginEditing -= OnShouldBeginEditing;
#endif
platformView.EditingDidBegin -= OnStarted;
platformView.EditingDidEnd -= OnEnded;
platformView.EditingChanged -= OnEditing;
}
#if MACCATALYST
bool OnShouldBeginEditing (UITextField textField)
{
if (Handler is not PickerHandler handler)
return false;
// The PlatformView will always be a MauiPicker (which derives from UITextfield) by definition, but we're forced to use
// UITextField as the parameter to satisfy the signature for ShouldBeginEditing. The following cast is safe.
handler.DisplayAlert((MauiPicker)textField);
return false;
}
#else
public void OnDone()
{
if (Handler is PickerHandler handler)
{
handler.OnDone();
}
}
#endif
void OnStarted(object? sender, EventArgs eventArgs)
{
if (VirtualView is IPicker virtualView)
virtualView.IsFocused = true;
}
void OnEnded(object? sender, EventArgs eventArgs)
{
if (Handler is not PickerHandler handler || handler._pickerView is not UIPickerView pickerView)
return;
PickerSource? model = (PickerSource)pickerView.Model;
if (model.SelectedIndex != -1 && model.SelectedIndex != pickerView.SelectedRowInComponent(0))
{
pickerView.Select(model.SelectedIndex, 0, false);
}
if (VirtualView is IPicker virtualView)
virtualView.IsFocused = false;
}
void OnEditing(object? sender, EventArgs eventArgs)
{
if (sender is not MauiPicker platformView || VirtualView is not IPicker virtualView)
return;
// Reset the TextField's Text so it appears as if typing with a keyboard does not work.
var selectedIndex = virtualView.SelectedIndex;
platformView.Text = virtualView.GetItem(selectedIndex);
// Also clears the undo stack (undo/redo possible on iPads)
platformView.UndoManager?.RemoveAllActions();
}
}
}
public class PickerSource : UIPickerViewModel
{
WeakReference<PickerHandler>? _weakReference;
public PickerSource(PickerHandler? handler)
{
Handler = handler;
}
public PickerHandler? Handler
{
get
{
if (_weakReference?.TryGetTarget(out PickerHandler? target) == true)
return target;
return null;
}
set
{
_weakReference = null;
if (value == null)
return;
_weakReference = new WeakReference<PickerHandler>(value);
}
}
public int SelectedIndex { get; internal set; }
public override nint GetComponentCount(UIPickerView picker) => 1;
public override nint GetRowsInComponent(UIPickerView pickerView, nint component) =>
Handler?.VirtualView?.GetCount() ?? 0;
public override string GetTitle(UIPickerView picker, nint row, nint component) =>
Handler?.VirtualView?.GetItem((int)row) ?? string.Empty;
public override void Selected(UIPickerView picker, nint row, nint component)
{
SelectedIndex = (int)row;
if (Handler != null && Handler.UpdateImmediately) // Platform Specific
{
var virtualView = Handler?.VirtualView;
var platformView = Handler?.PlatformView;
if (virtualView == null || platformView == null)
return;
platformView.UpdatePicker(virtualView, SelectedIndex);
}
}
}
} |