Compartilhar via


Exibições nativos em XAML

As exibições nativas do iOS, Android e da Plataforma Universal do Windows podem ser referenciadas diretamente de Xamarin.Forms arquivos XAML. As propriedades e os manipuladores de eventos podem ser definidos em modos de exibição nativos e podem interagir com Xamarin.Forms modos de exibição. Este artigo demonstra como consumir exibições nativas de Xamarin.Forms arquivos XAML.

Para incorporar um modo de exibição nativo em um Xamarin.Forms arquivo XAML:

  1. Adicione uma xmlns declaração de namespace no arquivo XAML para o namespace que contém o modo de exibição nativo.
  2. Crie uma instância do modo de exibição nativo no arquivo XAML.

Importante

O XAML compilado deve ser desabilitado para qualquer página XAML que use modos de exibição nativos. Isso pode ser feito decorando a classe code-behind para sua página XAML com o [XamlCompilation(XamlCompilationOptions.Skip)] atributo. Para obter mais informações sobre a compilação XAML, consulte Compilação XAML em Xamarin.Forms.

Para fazer referência a uma exibição nativa de um arquivo code-behind, você deve usar um SAP (Shared Asset Project) e encapsular o código específico da plataforma com diretivas de compilação condicional. Para obter mais informações, consulte Consulte modos de exibição nativos do código.

Consumir exibições nativas

O exemplo de código a seguir demonstra o consumo de exibições nativas para cada plataforma para um 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>

Além de especificar o clr-namespace e assembly para um namespace de exibição nativo, um targetPlatform também deve ser especificado. Isso deve ser definido como iOS, Android, , UWP( Windows que é equivalente a UWP), macOS, GTK, Tizenou WPF. Em tempo de execução, o analisador XAML ignorará quaisquer prefixos de namespace XML que tenham um targetPlatform que não corresponda à plataforma na qual o aplicativo está sendo executado.

Cada declaração de namespace pode ser usada para fazer referência a qualquer classe ou estrutura do namespace especificado. Por exemplo, a ios declaração de namespace pode ser usada para fazer referência a qualquer classe ou estrutura do namespace iOS UIKit . As propriedades do modo de exibição nativo podem ser definidas por meio de XAML, mas os tipos de propriedade e objeto devem corresponder. Por exemplo, a UILabel.TextColor propriedade é definida como UIColor.Red usando a extensão de x:Static marcação e o ios namespace.

As propriedades vinculáveis e as propriedades vinculáveis anexadas também podem ser definidas em exibições nativas usando a Class.BindableProperty="value" sintaxe. Cada exibição nativa é encapsulada em uma instância específica NativeViewWrapper da plataforma, que deriva da Xamarin.Forms.View classe. A definição de uma propriedade vinculável ou de uma propriedade vinculável anexada em um modo de exibição nativo transfere o valor da propriedade para o wrapper. Por exemplo, um layout horizontal centralizado pode ser especificado definindo View.HorizontalOptions="Center" no modo de exibição nativo.

Observação

Observe que os estilos não podem ser usados com modos de exibição nativos, porque os estilos só podem direcionar propriedades que são apoiadas por BindableProperty objetos.

Os construtores de widget Android geralmente exigem o objeto Android Context como um argumento, e isso pode ser disponibilizado por meio de uma propriedade estática na MainActivity classe. Portanto, ao criar um widget Android em XAML, o Context objeto geralmente deve ser passado para o construtor do widget usando o x:Arguments atributo com uma x:Static extensão de marcação. Para obter mais informações, consulte Passar argumentos para modos de exibição nativos.

Observação

Observe que nomear um modo de exibição nativo com x:Name não é possível em um projeto de biblioteca do .NET Standard ou em um projeto de ativo compartilhado (SAP). Isso gerará uma variável do tipo nativo, o que causará um erro de compilação. No entanto, as exibições nativas podem ser encapsuladas em ContentView instâncias e recuperadas no arquivo code-behind, desde que um SAP esteja sendo usado. Para obter mais informações, consulte Consulte o modo de exibição nativo do código.

Ligações nativas

A vinculação de dados é usada para sincronizar uma interface do usuário com sua fonte de dados e simplifica como um Xamarin.Forms aplicativo exibe e interage com seus dados. Desde que o objeto de origem implemente a INotifyPropertyChanged interface, as alterações no objeto de origem são automaticamente enviadas para o objeto de destino pela estrutura de vinculação e as alterações no objeto de destino podem, opcionalmente, ser enviadas por push para o objeto de origem .

As propriedades de modos de exibição nativos também podem usar a vinculação de dados. O exemplo de código a seguir demonstra a vinculação de dados usando propriedades de modos de exibição nativos:

<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>

A página contém uma Entry propriedade cuja IsEnabled se vincula NativeSwitchPageViewModel.IsSwitchOn à propriedade. O BindingContext da página é definido como uma nova instância da NativeSwitchPageViewModel classe no arquivo code-behind, com a classe ViewModel implementando a INotifyPropertyChanged interface.

A página também contém um switch nativo para cada plataforma. Cada comutador nativo usa uma TwoWay associação para atualizar o valor da NativeSwitchPageViewModel.IsSwitchOn propriedade. Portanto, quando o interruptor está desligado, o Entry é desativado e, quando o interruptor está ligado, o Entry é habilitado. As capturas de tela a seguir mostram essa funcionalidade em cada plataforma:

Comutador nativo desativadoSwitch nativo habilitado

As ligações bidirecionais são suportadas automaticamente, desde que a propriedade nativa implemente INotifyPropertyChangedou ofereça suporte à Observação de Chave-Valor (KVO) no iOS ou seja uma DependencyProperty UWP. No entanto, muitos modos de exibição nativos não oferecem suporte à notificação de alteração de propriedade. Para esses modos de exibição, você pode especificar um UpdateSourceEventName valor de propriedade como parte da expressão de vinculação. Essa propriedade deve ser definida como o nome de um evento no modo de exibição nativo que sinaliza quando a propriedade de destino foi alterada. Em seguida, quando o valor do comutador nativo é alterado, a Binding classe é notificada de que o usuário alterou o valor do switch e o valor da NativeSwitchPageViewModel.IsSwitchOn propriedade é atualizado.

Passar argumentos para modos de exibição nativos

Os argumentos do construtor podem ser passados para exibições nativas usando o x:Arguments atributo com uma x:Static extensão de marcação. Além disso, os métodos de fábrica de exibição nativa (public static métodos que retornam objetos ou valores do mesmo tipo que a classe ou estrutura que define os métodos) podem ser chamados especificando o nome do método usando o x:FactoryMethod atributo e seus argumentos usando o x:Arguments atributo.

O exemplo de código a seguir demonstra ambas as 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>

O UIFont.FromName método factory é usado para definir a UILabel.Font propriedade como um novo UIFont no iOS. O UIFont nome e o tamanho são especificados pelos argumentos do método que são filhos do x:Arguments atributo.

O Typeface.Create método factory é usado para definir a TextView.Typeface propriedade como um novo Typeface no Android. O Typeface nome da família e o estilo são especificados pelos argumentos do método que são filhos do x:Arguments atributo.

O FontFamily construtor é usado para definir a TextBlock.FontFamily propriedade para um novo FontFamily na Plataforma Universal do Windows (UWP). O FontFamily nome é especificado pelo argumento method que é filho do x:Arguments atributo.

Observação

Os argumentos devem corresponder aos tipos exigidos pelo construtor ou método de fábrica.

As capturas de tela a seguir mostram o resultado da especificação do método de fábrica e dos argumentos do construtor para definir a fonte em diferentes modos de exibição nativos:

Definindo fontes em modos de exibição nativos

Para obter mais informações sobre como passar argumentos em XAML, consulte Passando argumentos em XAML.

Consulte as exibições nativas do código

Embora não seja possível nomear um modo de exibição nativo com o x:Name atributo, é possível recuperar uma instância de exibição nativa declarada em um arquivo XAML de seu arquivo code-behind em um Projeto de Acesso Compartilhado, desde que o modo de exibição nativo seja filho de um ContentView que especifique um x:Name valor de atributo. Em seguida, dentro de diretivas de compilação condicional no arquivo code-behind, você deve:

  1. Recupere o valor da propriedade e converta-o ContentView.Content em um tipo específico NativeViewWrapper de plataforma.
  2. Recupere a NativeViewWrapper.NativeElement propriedade e converta-a no tipo de exibição nativo.

A API nativa pode então ser invocada na exibição nativa para executar as operações desejadas. Essa abordagem também oferece o benefício de que várias exibições nativas XAML para diferentes plataformas podem ser filhas do mesmo ContentView. O exemplo de código a seguir demonstra essa 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>

No exemplo acima, as exibições nativas de cada plataforma são filhas de controles, com o x:Name valor do ContentView atributo sendo usado para recuperar o ContentView code-behind:

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;
    }
}

A ContentView.Content propriedade é acessada para recuperar a exibição nativa encapsulada como uma instância específica NativeViewWrapper da plataforma. A NativeViewWrapper.NativeElement propriedade é acessada para recuperar o modo de exibição nativo como seu tipo nativo. A API da exibição nativa é então invocada para executar as operações desejadas.

Os botões nativos do iOS e do Android compartilham o mesmo OnButtonTap manipulador de eventos, porque cada botão nativo consome um EventHandler representante em resposta a um evento de toque. No entanto, a Plataforma Universal do Windows (UWP) usa um , separado RoutedEventHandlerque, por sua vez, consome o OnButtonTap manipulador de eventos neste exemplo. Portanto, quando um botão nativo é clicado, o manipulador de eventos é executado, o OnButtonTap que dimensiona e gira o controle nativo contido no ContentView nome contentViewTextParent. As capturas de tela a seguir demonstram que isso ocorre em cada plataforma:

ContentView contendo um controle nativo

Modos de exibição nativos de subclasse

Muitos modos de exibição nativos do iOS e Android não são adequados para instanciação em XAML porque usam métodos, em vez de propriedades, para configurar o controle. A solução para esse problema é subclassificar exibições nativas em wrappers que definem uma API mais amigável a XAML que usa propriedades para configurar o controle e que usa eventos independentes de plataforma. As exibições nativas encapsuladas podem ser colocadas em um SAP (Shared Asset Project) e cercadas com diretivas de compilação condicional ou colocadas em projetos específicos da plataforma e referenciadas a partir de XAML em um projeto de biblioteca do .NET Standard.

O exemplo de código a seguir demonstra uma Xamarin.Forms página que consome modos de exibição nativos subclassificados:

<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>

A página contém um que exibe a Label fruta escolhida pelo usuário a partir de um controle nativo. O Label vincula ao SubclassedNativeControlsPageViewModel.SelectedFruit imóvel. O BindingContext da página é definido como uma nova instância da SubclassedNativeControlsPageViewModel classe no arquivo code-behind, com a classe ViewModel implementando a INotifyPropertyChanged interface.

A página também contém uma exibição de seletor nativa para cada plataforma. Cada exibição nativa exibe a coleção de frutas vinculando sua ItemSource propriedade à SubclassedNativeControlsPageViewModel.Fruits coleção. Isso permite que o usuário escolha uma fruta, como mostrado nas seguintes capturas de tela:

Exibições nativas subclassificadas

No iOS e Android, os selecionadores nativos usam métodos para configurar os controles. Portanto, esses seletores devem ser subclassificados para expor propriedades para torná-los compatíveis com XAML. Na Plataforma Universal do Windows (UWP), o ComboBox já é compatível com XAML e, portanto, não requer subclassificação.

iOS

A implementação do iOS subclasses a UIPickerView exibição e expõe propriedades e um evento que pode ser facilmente consumido de 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 { }
    }
}

A MyUIPickerView classe expõe ItemsSource e SelectedItem propriedades, e um SelectedItemChanged evento. A UIPickerView requer um modelo de dados subjacente UIPickerViewModel , que é acessado pelas propriedades e pelo MyUIPickerView evento. O UIPickerViewModel modelo de dados é fornecido pela PickerModel classe:

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());
        }
    }
}

A PickerModel classe fornece o armazenamento subjacente para a MyUIPickerView classe, por meio da Items propriedade. Sempre que o item selecionado nas MyUIPickerView alterações, o Selected método é executado, o que atualiza o índice selecionado e dispara o ItemChanged evento. Isso garante que a SelectedItem propriedade sempre retornará o último item escolhido pelo usuário. Além disso, a PickerModel classe substitui os métodos usados para configurar a MyUIPickerView instância.

Android

A implementação do Android subclasse a Spinner exibição e expõe propriedades e um evento que pode ser facilmente consumido de 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);
    }
}

A MySpinner classe expõe ItemsSource e SelectedObject propriedades, e um ItemSelected evento. Os itens exibidos pela MySpinner classe são fornecidos pelo Adapter associado ao modo de exibição, e os itens são preenchidos Adapter no quando a ItemsSource propriedade é definida pela primeira vez. Sempre que o item selecionado na MySpinner classe é alterado, o OnBindableSpinnerItemSelected manipulador de eventos atualiza a SelectedObject propriedade.