Vistas nativas en XAML

Descargar ejemploDescargar el ejemplo

Se puede hacer referencia directamente a las vistas nativas de iOS, Android y el Plataforma universal de Windows desde Xamarin.Forms archivos XAML. Las propiedades y los controladores de eventos se pueden establecer en vistas nativas y pueden interactuar con Xamarin.Forms las vistas. En este artículo se muestra cómo consumir vistas nativas de Xamarin.Forms archivos XAML.

Para insertar una vista nativa en un Xamarin.Forms archivo XAML:

  1. Agregue una xmlns declaración de espacio de nombres en el archivo XAML para el espacio de nombres que contiene la vista nativa.
  2. Cree una instancia de la vista nativa en el archivo XAML.

Importante

Xaml compilado debe estar deshabilitado para las páginas XAML que usen vistas nativas. Esto se puede lograr mediante la decoración de la clase de código subyacente para la página XAML con el [XamlCompilation(XamlCompilationOptions.Skip)] atributo . Para obtener más información sobre la compilación XAML, consulta Compilación XAML en Xamarin.Forms.

Para hacer referencia a una vista nativa desde un archivo de código subyacente, debe usar un proyecto de recursos compartidos (SAP) y encapsular el código específico de la plataforma con directivas de compilación condicional. Para obtener más información, consulte Referencia a vistas nativas desde el código.

Consumo de vistas nativas

En el ejemplo de código siguiente se muestra cómo consumir vistas nativas para cada plataforma en :Xamarin.FormsContentPage

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
        xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
        xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
            Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        x:Class="NativeViews.NativeViewDemo">
    <StackLayout Margin="20">
        <ios:UILabel Text="Hello World" TextColor="{x:Static ios:UIColor.Red}" View.HorizontalOptions="Start" />
        <androidWidget:TextView Text="Hello World" x:Arguments="{x:Static androidLocal:MainActivity.Instance}" />
        <win:TextBlock Text="Hello World" />
    </StackLayout>
</ContentPage>

Además de especificar y clr-namespaceassembly para un espacio de nombres de vista nativo, targetPlatform también se debe especificar . Debe establecerse iOSen , Android, UWP, Windows (que es equivalente a UWP), macOS, GTK, Tizeno WPF. En tiempo de ejecución, el analizador XAML omitirá los prefijos de espacio de nombres XML que tengan un targetPlatform que no coincida con la plataforma en la que se ejecuta la aplicación.

Cada declaración de espacio de nombres se puede usar para hacer referencia a cualquier clase o estructura del espacio de nombres especificado. Por ejemplo, la ios declaración de espacio de nombres se puede usar para hacer referencia a cualquier clase o estructura del espacio de nombres de iOS UIKit . Las propiedades de la vista nativa se pueden establecer a través de XAML, pero los tipos de propiedad y objeto deben coincidir. Por ejemplo, la UILabel.TextColor propiedad se establece en UIColor.Red mediante la x:Static extensión de marcado y el ios espacio de nombres .

Las propiedades enlazables y las propiedades enlazables adjuntas también se pueden establecer en vistas nativas mediante la Class.BindableProperty="value" sintaxis . Cada vista nativa se ajusta en una instancia específica NativeViewWrapper de la plataforma, que se deriva de la Xamarin.Forms.View clase . Establecer una propiedad enlazable o una propiedad enlazable adjunta en una vista nativa transfiere el valor de propiedad al contenedor. Por ejemplo, se puede especificar un diseño horizontal centrado estableciendo View.HorizontalOptions="Center" en la vista nativa.

Nota

Tenga en cuenta que los estilos no se pueden usar con vistas nativas, ya que los estilos solo pueden tener como destino propiedades respaldadas por BindableProperty objetos .

Por lo general, los constructores de widgets de Android requieren el objeto Android Context como argumento y esto se puede poner a disposición a través de una propiedad estática en la MainActivity clase . Por lo tanto, al crear un widget de Android en XAML, el Context objeto normalmente se debe pasar al constructor del widget mediante el x:Arguments atributo con una x:Static extensión de marcado. Para obtener más información, vea Pasar argumentos a vistas nativas.

Nota

Tenga en cuenta que no es posible asignar un nombre a una vista nativa con x:Name en un proyecto de biblioteca de .NET Standard o en un proyecto de recursos compartidos (SAP). Al hacerlo, se generará una variable del tipo nativo, lo que provocará un error de compilación. Sin embargo, las vistas nativas se pueden encapsular en ContentView instancias y recuperarse en el archivo de código subyacente, siempre que se use una instancia de SAP. Para obtener más información, consulte Referencia a la vista nativa desde el código.

Enlaces nativos

El enlace de datos se usa para sincronizar una interfaz de usuario con su origen de datos y simplifica la forma en que una Xamarin.Forms aplicación muestra e interactúa con sus datos. Siempre que el objeto de origen implemente la INotifyPropertyChanged interfaz, los cambios en el objeto de origen se insertan automáticamente en el objeto de destino mediante el marco de enlace y los cambios en el objeto de destino se pueden insertar opcionalmente en el objeto de origen.

Las propiedades de las vistas nativas también pueden usar el enlace de datos. En el ejemplo de código siguiente se muestra el enlace de datos mediante propiedades de vistas nativas:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
        xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
        xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
            Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:local="clr-namespace:NativeSwitch"
        x:Class="NativeSwitch.NativeSwitchPage">
    <StackLayout Margin="20">
        <Label Text="Native Views Demo" FontAttributes="Bold" HorizontalOptions="Center" />
        <Entry Placeholder="This Entry is bound to the native switch" IsEnabled="{Binding IsSwitchOn}" />
        <ios:UISwitch On="{Binding Path=IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=ValueChanged}"
            OnTintColor="{x:Static ios:UIColor.Red}"
            ThumbTintColor="{x:Static ios:UIColor.Blue}" />
        <androidWidget:Switch x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
            Checked="{Binding Path=IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=CheckedChange}"
            Text="Enable Entry?" />
        <win:ToggleSwitch Header="Enable Entry?"
            OffContent="No"
            OnContent="Yes"
            IsOn="{Binding IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=Toggled}" />
    </StackLayout>
</ContentPage>

La página contiene una Entry cuya IsEnabled propiedad se enlaza a la NativeSwitchPageViewModel.IsSwitchOn propiedad . La BindingContext propiedad de la página se establece en una nueva instancia de la NativeSwitchPageViewModel clase en el archivo de código subyacente, con la clase ViewModel que implementa la INotifyPropertyChanged interfaz .

La página también contiene un modificador nativo para cada plataforma. Cada conmutador nativo usa un TwoWay enlace para actualizar el valor de la NativeSwitchPageViewModel.IsSwitchOn propiedad. Por lo tanto, cuando el conmutador está desactivado, Entry está deshabilitado y, cuando el conmutador está activado, Entry está habilitado. En las capturas de pantalla siguientes se muestra esta funcionalidad en cada plataforma:

Conmutadornativo deshabilitado Conmutador nativo habilitado

Los enlaces bidireccionales se admiten automáticamente siempre que la propiedad nativa implemente INotifyPropertyChangedo admita Key-Value Observación (KVO) en iOS o sea en DependencyProperty UWP. Sin embargo, muchas vistas nativas no admiten la notificación de cambio de propiedad. Para estas vistas, puede especificar un UpdateSourceEventName valor de propiedad como parte de la expresión de enlace. Esta propiedad debe establecerse en el nombre de un evento en la vista nativa que indica cuándo ha cambiado la propiedad de destino. A continuación, cuando cambia el valor del modificador nativo, se notifica a la Binding clase que el usuario ha cambiado el valor del modificador y se actualiza el valor de la NativeSwitchPageViewModel.IsSwitchOn propiedad.

Pasar argumentos a vistas nativas

Los argumentos del constructor se pueden pasar a vistas nativas mediante el x:Arguments atributo con una x:Static extensión de marcado. Además, se puede llamar a los métodos de generador de vistas nativas (public static métodos que devuelven objetos o valores del mismo tipo que la clase o estructura que define los métodos) especificando el nombre del método mediante el x:FactoryMethod atributo y sus argumentos mediante el x:Arguments atributo .

En el ejemplo de código siguiente se muestran ambas técnicas:

<ContentPage ...
        xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
        xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidGraphics="clr-namespace:Android.Graphics;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
        xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:winMedia="clr-namespace:Windows.UI.Xaml.Media;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:winText="clr-namespace:Windows.UI.Text;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:winui="clr-namespace:Windows.UI;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows">
        ...
        <ios:UILabel Text="Simple Native Color Picker" View.HorizontalOptions="Center">
            <ios:UILabel.Font>
                <ios:UIFont x:FactoryMethod="FromName">
                    <x:Arguments>
                        <x:String>Papyrus</x:String>
                        <x:Single>24</x:Single>
                    </x:Arguments>
                </ios:UIFont>
            </ios:UILabel.Font>
        </ios:UILabel>
        <androidWidget:TextView x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
                    Text="Simple Native Color Picker"
                    TextSize="24"
                    View.HorizontalOptions="Center">
            <androidWidget:TextView.Typeface>
                <androidGraphics:Typeface x:FactoryMethod="Create">
                    <x:Arguments>
                        <x:String>cursive</x:String>
                        <androidGraphics:TypefaceStyle>Normal</androidGraphics:TypefaceStyle>
                    </x:Arguments>
                </androidGraphics:Typeface>
            </androidWidget:TextView.Typeface>
        </androidWidget:TextView>
        <winControls:TextBlock Text="Simple Native Color Picker"
                    FontSize="20"
                    FontStyle="{x:Static winText:FontStyle.Italic}"
                    View.HorizontalOptions="Center">
            <winControls:TextBlock.FontFamily>
                <winMedia:FontFamily>
                    <x:Arguments>
                        <x:String>Georgia</x:String>
                    </x:Arguments>
                </winMedia:FontFamily>
            </winControls:TextBlock.FontFamily>
        </winControls:TextBlock>
        ...
</ContentPage>

El UIFont.FromName método factory se usa para establecer la UILabel.Font propiedad en un nuevo UIFont en iOS. El UIFont nombre y el tamaño se especifican mediante los argumentos del método que son elementos secundarios del x:Arguments atributo .

El Typeface.Create método factory se usa para establecer la TextView.Typeface propiedad en un nuevo Typeface en Android. El Typeface nombre de familia y el estilo se especifican mediante los argumentos del método que son elementos secundarios del x:Arguments atributo.

El FontFamily constructor se usa para establecer la TextBlock.FontFamily propiedad en un nuevo FontFamily en el Plataforma universal de Windows (UWP). El FontFamily nombre se especifica mediante el argumento de método que es un elemento secundario del x:Arguments atributo .

Nota

Los argumentos deben coincidir con los tipos requeridos por el constructor o el método factory.

En las capturas de pantalla siguientes se muestra el resultado de especificar argumentos de constructor y método de fábrica para establecer la fuente en diferentes vistas nativas:

Establecer fuentes en vistas nativas

Para obtener más información sobre cómo pasar argumentos en XAML, consulta Pasar argumentos en XAML.

Consulte las vistas nativas desde el código.

Aunque no es posible asignar un nombre a una vista nativa con el x:Name atributo , es posible recuperar una instancia de vista nativa declarada en un archivo XAML de su archivo de código subyacente en un proyecto de acceso compartido, siempre que la vista nativa sea un elemento secundario de un ContentView que especifique un x:Name valor de atributo. A continuación, dentro de las directivas de compilación condicional en el archivo de código subyacente debe:

  1. Recupere el valor de la ContentView.Content propiedad y consértelo en un tipo específico NativeViewWrapper de la plataforma.
  2. Recupere la NativeViewWrapper.NativeElement propiedad y consértala al tipo de vista nativa.

A continuación, se puede invocar la API nativa en la vista nativa para realizar las operaciones deseadas. Este enfoque también ofrece la ventaja de que varias vistas nativas xaml para distintas plataformas pueden ser elementos secundarios del mismo ContentView. En el ejemplo de código siguiente se muestra esta técnica:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
        xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
        xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
            Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:local="clr-namespace:NativeViewInsideContentView"
        x:Class="NativeViewInsideContentView.NativeViewInsideContentViewPage">
    <StackLayout Margin="20">
        <ContentView x:Name="contentViewTextParent" HorizontalOptions="Center" VerticalOptions="CenterAndExpand">
            <ios:UILabel Text="Text in a UILabel" TextColor="{x:Static ios:UIColor.Red}" />
            <androidWidget:TextView x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
                Text="Text in a TextView" />
              <winControls:TextBlock Text="Text in a TextBlock" />
        </ContentView>
        <ContentView x:Name="contentViewButtonParent" HorizontalOptions="Center" VerticalOptions="EndAndExpand">
            <ios:UIButton TouchUpInside="OnButtonTap" View.HorizontalOptions="Center" View.VerticalOptions="Center" />
            <androidWidget:Button x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
                Text="Scale and Rotate Text"
                Click="OnButtonTap" />
            <winControls:Button Content="Scale and Rotate Text" />
        </ContentView>
    </StackLayout>
</ContentPage>

En el ejemplo anterior, las vistas nativas de cada plataforma son elementos secundarios de controles, con el valor de ContentViewx:Name atributo que se usa para recuperar en ContentView el código subyacente:

public partial class NativeViewInsideContentViewPage : ContentPage
{
    public NativeViewInsideContentViewPage()
    {
        InitializeComponent();

#if __IOS__
        var wrapper = (Xamarin.Forms.Platform.iOS.NativeViewWrapper)contentViewButtonParent.Content;
        var button = (UIKit.UIButton)wrapper.NativeView;
        button.SetTitle("Scale and Rotate Text", UIKit.UIControlState.Normal);
        button.SetTitleColor(UIKit.UIColor.Black, UIKit.UIControlState.Normal);
#endif
#if __ANDROID__
        var wrapper = (Xamarin.Forms.Platform.Android.NativeViewWrapper)contentViewTextParent.Content;
        var textView = (Android.Widget.TextView)wrapper.NativeView;
        textView.SetTextColor(Android.Graphics.Color.Red);
#endif
#if WINDOWS_UWP
        var textWrapper = (Xamarin.Forms.Platform.UWP.NativeViewWrapper)contentViewTextParent.Content;
        var textBlock = (Windows.UI.Xaml.Controls.TextBlock)textWrapper.NativeElement;
        textBlock.Foreground = new Windows.UI.Xaml.Media.SolidColorBrush(Windows.UI.Colors.Red);
        var buttonWrapper = (Xamarin.Forms.Platform.UWP.NativeViewWrapper)contentViewButtonParent.Content;
        var button = (Windows.UI.Xaml.Controls.Button)buttonWrapper.NativeElement;
        button.Click += (sender, args) => OnButtonTap(sender, EventArgs.Empty);
#endif
    }

    async void OnButtonTap(object sender, EventArgs e)
    {
        contentViewButtonParent.Content.IsEnabled = false;
        contentViewTextParent.Content.ScaleTo(2, 2000);
        await contentViewTextParent.Content.RotateTo(360, 2000);
        contentViewTextParent.Content.ScaleTo(1, 2000);
        await contentViewTextParent.Content.RelRotateTo(360, 2000);
        contentViewButtonParent.Content.IsEnabled = true;
    }
}

Se ContentView.Content tiene acceso a la propiedad para recuperar la vista nativa ajustada como una instancia específica NativeViewWrapper de la plataforma. A NativeViewWrapper.NativeElement continuación, se obtiene acceso a la propiedad para recuperar la vista nativa como su tipo nativo. A continuación, se invoca la API de la vista nativa para realizar las operaciones deseadas.

Los botones nativos de iOS y Android comparten el mismo OnButtonTap controlador de eventos, ya que cada botón nativo consume un EventHandler delegado en respuesta a un evento táctil. Sin embargo, el Plataforma universal de Windows (UWP) usa un elemento independiente RoutedEventHandler, que a su vez consume el OnButtonTap controlador de eventos en este ejemplo. Por lo tanto, cuando se hace clic en un botón nativo, se ejecuta el OnButtonTap controlador de eventos, que escala y gira el control nativo contenido en el ContentView denominado contentViewTextParent. Las capturas de pantalla siguientes muestran que esto se produce en cada plataforma:

ContentView que contiene un control nativo

Vistas nativas de subclase

Muchas vistas nativas de iOS y Android no son adecuadas para crear instancias en XAML porque usan métodos, en lugar de propiedades, para configurar el control. La solución a este problema consiste en subclases de vistas nativas en contenedores que definen una API más fácil de usar XAML que usa propiedades para configurar el control y que usa eventos independientes de la plataforma. Las vistas nativas ajustadas se pueden colocar en un proyecto de recursos compartidos (SAP) y estar rodeadas de directivas de compilación condicional, o colocarse en proyectos específicos de la plataforma y hacer referencia a ellos desde XAML en un proyecto de biblioteca de .NET Standard.

En el ejemplo de código siguiente se muestra una Xamarin.Forms página que consume vistas nativas con subclases:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
        xmlns:iosLocal="clr-namespace:SubclassedNativeControls.iOS;assembly=SubclassedNativeControls.iOS;targetPlatform=iOS"
        xmlns:android="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SubclassedNativeControls.Droid;assembly=SubclassedNativeControls.Droid;targetPlatform=Android"
        xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
            Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:local="clr-namespace:SubclassedNativeControls"
        x:Class="SubclassedNativeControls.SubclassedNativeControlsPage">
    <StackLayout Margin="20">
        <Label Text="Subclassed Native Views Demo" FontAttributes="Bold" HorizontalOptions="Center" />
        <StackLayout Orientation="Horizontal">
          <Label Text="You have chosen:" />
          <Label Text="{Binding SelectedFruit}" />      
        </StackLayout>
        <iosLocal:MyUIPickerView ItemsSource="{Binding Fruits}"
            SelectedItem="{Binding SelectedFruit, Mode=TwoWay, UpdateSourceEventName=SelectedItemChanged}" />
        <androidLocal:MySpinner x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
            ItemsSource="{Binding Fruits}"
            SelectedObject="{Binding SelectedFruit, Mode=TwoWay, UpdateSourceEventName=ItemSelected}" />
        <winControls:ComboBox ItemsSource="{Binding Fruits}"
            SelectedItem="{Binding SelectedFruit, Mode=TwoWay, UpdateSourceEventName=SelectionChanged}" />
    </StackLayout>
</ContentPage>

La página contiene un Label objeto que muestra la fruta elegida por el usuario desde un control nativo. Enlaza Label a la SubclassedNativeControlsPageViewModel.SelectedFruit propiedad . La BindingContext propiedad de la página se establece en una nueva instancia de la SubclassedNativeControlsPageViewModel clase en el archivo de código subyacente, con la clase ViewModel que implementa la INotifyPropertyChanged interfaz .

La página también contiene una vista de selector nativa para cada plataforma. Cada vista nativa muestra la colección de frutas enlazando su ItemSource propiedad a la SubclassedNativeControlsPageViewModel.Fruits colección. Esto permite al usuario elegir una fruta, como se muestra en las capturas de pantalla siguientes:

Vistas nativas con subclases

En iOS y Android, los selectores nativos usan métodos para configurar los controles. Por lo tanto, estos selectores deben estar subclases para exponer propiedades para que sean compatibles con XAML. En el Plataforma universal de Windows (UWP), ya ComboBox es compatible con XAML, por lo que no requiere subclases.

iOS

La implementación de iOS subclase la UIPickerView vista y expone propiedades y un evento que se puede consumir fácilmente desde XAML:

public class MyUIPickerView : UIPickerView
{
    public event EventHandler<EventArgs> SelectedItemChanged;

    public MyUIPickerView()
    {
        var model = new PickerModel();
        model.ItemChanged += (sender, e) =>
        {
            if (SelectedItemChanged != null)
            {
                SelectedItemChanged.Invoke(this, e);
            }
        };
        Model = model;
    }

    public IList<string> ItemsSource
    {
        get
        {
            var pickerModel = Model as PickerModel;
            return (pickerModel != null) ? pickerModel.Items : null;
        }
        set
        {
            var model = Model as PickerModel;
            if (model != null)
            {
                model.Items = value;
            }
        }
    }

    public string SelectedItem
    {
        get { return (Model as PickerModel).SelectedItem; }
        set { }
    }
}

La MyUIPickerView clase expone ItemsSource las propiedades y SelectedItem , y un SelectedItemChanged evento . Un UIPickerView requiere un modelo de datos subyacente UIPickerViewModel , al que acceden las propiedades y el MyUIPickerView evento . La UIPickerViewModel clase proporciona el PickerModel modelo de datos:

class PickerModel : UIPickerViewModel
{
    int selectedIndex = 0;
    public event EventHandler<EventArgs> ItemChanged;
    public IList<string> Items { get; set; }

    public string SelectedItem
    {
        get
        {
            return Items != null && selectedIndex >= 0 && selectedIndex < Items.Count ? Items[selectedIndex] : null;
        }
    }

    public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
    {
        return Items != null ? Items.Count : 0;
    }

    public override string GetTitle(UIPickerView pickerView, nint row, nint component)
    {
        return Items != null && Items.Count > row ? Items[(int)row] : null;
    }

    public override nint GetComponentCount(UIPickerView pickerView)
    {
        return 1;
    }

    public override void Selected(UIPickerView pickerView, nint row, nint component)
    {
        selectedIndex = (int)row;
        if (ItemChanged != null)
        {
            ItemChanged.Invoke(this, new EventArgs());
        }
    }
}

La PickerModel clase proporciona el almacenamiento subyacente para la MyUIPickerView clase , a través de la Items propiedad . Cada vez que cambia el elemento seleccionado, MyUIPickerView se ejecuta el Selected método , que actualiza el índice seleccionado y desencadena el ItemChanged evento. Esto garantiza que la SelectedItem propiedad siempre devolverá el último elemento seleccionado por el usuario. Además, la PickerModel clase invalida los métodos que se usan para configurar la MyUIPickerView instancia.

Android

La implementación de Android subclase la Spinner vista y expone propiedades y un evento que se puede consumir fácilmente desde XAML:

class MySpinner : Spinner
{
    ArrayAdapter adapter;
    IList<string> items;

    public IList<string> ItemsSource
    {
        get { return items; }
        set
        {
            if (items != value)
            {
                items = value;
                adapter.Clear();

                foreach (string str in items)
                {
                    adapter.Add(str);
                }
            }
        }
    }

    public string SelectedObject
    {
        get { return (string)GetItemAtPosition(SelectedItemPosition); }
        set
        {
            if (items != null)
            {
                int index = items.IndexOf(value);
                if (index != -1)
                {
                    SetSelection(index);
                }
            }
        }
    }

    public MySpinner(Context context) : base(context)
    {
        ItemSelected += OnBindableSpinnerItemSelected;

        adapter = new ArrayAdapter(context, Android.Resource.Layout.SimpleSpinnerItem);
        adapter.SetDropDownViewResource(Android.Resource.Layout.SimpleSpinnerDropDownItem);
        Adapter = adapter;
    }

    void OnBindableSpinnerItemSelected(object sender, ItemSelectedEventArgs args)
    {
        SelectedObject = (string)GetItemAtPosition(args.Position);
    }
}

La MySpinner clase expone ItemsSource las propiedades y SelectedObject , y un ItemSelected evento . Los elementos mostrados por la MySpinner clase se proporcionan mediante el Adapter asociado a la vista y los elementos se rellenan en cuando Adapter se establece la ItemsSource propiedad por primera vez. Cada vez que cambia el elemento seleccionado de la MySpinner clase, el OnBindableSpinnerItemSelected controlador de eventos actualiza la SelectedObject propiedad .