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. Propriedades e manipuladores de eventos podem ser definidos em exibições nativas e podem interagir com Xamarin.Forms exibições. Este artigo demonstra como consumir exibições nativas de Xamarin.Forms arquivos XAML.

Para inserir uma exibição nativa em um Xamarin.Forms arquivo XAML:

  1. Adicione uma declaração de xmlns 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 todas as páginas XAML que usam exibições nativas. 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 no Xamarin.Forms.

Para fazer referência a uma exibição nativa de um arquivo code-behind, você deve usar um Projeto de Ativo Compartilhado (SAP) e encapsular o código específico da plataforma com diretivas de compilação condicional. Para obter mais informações, consulte Consultar exibições nativas 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, Tizen, ou WPF. Em runtime, o analisador XAML ignorará todos os 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 declaração de ios namespace pode ser usada para fazer referência a qualquer classe ou estrutura do namespace do 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 associáveis e as propriedades associá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. Definir uma propriedade associável ou uma propriedade associável anexada em uma exibição nativa transfere o valor da propriedade para o wrapper. Por exemplo, um layout horizontal centralizado pode ser especificado configurando View.HorizontalOptions="Center" a visualização nativa.

Observação

Observe que os estilos não podem ser usados com exibições nativas, pois os estilos só podem direcionar propriedades que são apoiadas por BindableProperty objetos.

Os construtores de widget do Android geralmente exigem o objeto Android Context como um argumento, e isso pode ser disponibilizado MainActivity por meio de uma propriedade estática na classe. Portanto, ao criar um widget do 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 exibições nativas.

Observação

Observe que nomear uma exibição nativa não x:Name é possível em um projeto de biblioteca .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 a exibição nativa do código.

Associações nativas

A associação de dados é usada para sincronizar uma interface do usuário com sua fonte de dados e simplifica a forma 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 por push para o objeto de destino pela estrutura de associação e as alterações no objeto de destino podem, opcionalmente, ser enviadas por push para o objeto de origem .

As propriedades de exibições nativas 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 exibições 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>

A página contém uma Entry propriedade whose IsEnabled se associa à NativeSwitchPageViewModel.IsSwitchOn propriedade. A BindingContext página é definida 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 uma opção nativa para cada plataforma. Cada opção nativa usa uma TwoWay associação para atualizar o valor da NativeSwitchPageViewModel.IsSwitchOn propriedade. Portanto, quando a chave está desligada, a é desabilitada Entry e, quando a chave está ligada, a Entry é habilitada. As capturas de tela a seguir mostram essa funcionalidade em cada plataforma:

Switch nativo desativadoSwitch nativo habilitado

As associações bidirecionais têm suporte automático, desde que a propriedade nativa implemente INotifyPropertyChanged, ou dê suporte à Observação de Chave-Valor (KVO) no iOS ou seja uma DependencyProperty na UWP. No entanto, muitas exibições nativas não dão suporte à notificação de alteração de propriedade. Para essas exibições, você pode especificar um UpdateSourceEventName valor de propriedade como parte da expressão de associação. Essa propriedade deve ser definida como o nome de um evento na exibição nativa que sinaliza quando a propriedade de destino foi alterada. Em seguida, quando o valor do switch nativo é alterado, a classe é notificada Binding de que o usuário alterou o valor do switch e o valor da NativeSwitchPageViewModel.IsSwitchOn propriedade é atualizado.

Passar argumentos para visualizações nativas

Os argumentos do construtor podem ser passados para exibições nativas usando o x:Arguments atributo com uma extensão de x:Static marcação. Além disso, os métodos de fábrica de exibição nativos (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 de fábrica é usado para definir a UILabel.Font propriedade como new UIFont no iOS. O UIFont nome e o tamanho são especificados pelos argumentos de método que são filhos do x:Arguments atributo.

O Typeface.Create método de fábrica é usado para definir a TextView.Typeface propriedade como new Typeface no Android. O Typeface nome e o estilo da família 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 como um novo FontFamily na Plataforma Universal do Windows (UWP). O FontFamily nome é especificado pelo argumento de método 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 de argumentos de método de fábrica e construtor para definir a fonte em diferentes exibições nativas:

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 uma exibição nativa 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 a exibição nativa seja um filho de um ContentView que especifica um x:Name valor de atributo. Em seguida, dentro das diretivas de compilação condicional no arquivo code-behind, você deve:

  1. Recupere o valor da ContentView.Content propriedade e converta-o em um tipo específico NativeViewWrapper da 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 visualizaçã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 filhos de controles, com o valor do ContentView x:Name atributo sendo usado para recuperar o ContentView no 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. Em seguida, a NativeViewWrapper.NativeElement propriedade é acessada para recuperar a exibição nativa como seu tipo nativo. A API da visualizaçã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, pois cada botão nativo consome um EventHandler delegado em resposta a um evento de toque. No entanto, a Plataforma Universal do Windows (UWP) usa um RoutedEventHandler, que, por sua vez, consome o OnButtonTap manipulador de eventos neste exemplo. Portanto, quando um botão nativo é clicado, o OnButtonTap manipulador de eventos é executado, o que dimensiona e gira o controle nativo contido no ContentView .contentViewTextParent As capturas de tela a seguir demonstram que isso ocorre em cada plataforma:

ContentView contendo um controle nativo

Visualizações nativas de subclasse

Muitas exibições nativas do iOS e do Android não são adequadas para instanciação em XAML porque usam métodos, em vez de propriedades, para configurar o controle. A solução para esse problema é criar uma subclasse de exibições nativas em wrappers que definem uma API mais amigável para 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 (Projeto de Ativo Compartilhado) e cercadas por diretivas de compilação condicional ou colocadas em projetos específicos da plataforma e referenciadas do XAML em um projeto de biblioteca do .NET Standard.

O exemplo de código a seguir demonstra uma Xamarin.Forms página que consome exibições nativas de subclasse:

<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 Label que exibe a fruta escolhida pelo usuário de um controle nativo. O Label vincula-se SubclassedNativeControlsPageViewModel.SelectedFruit à propriedade. A BindingContext página é definida 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 visualizaçã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 colha uma fruta, conforme mostrado nas capturas de tela a seguir:

Visualizações nativas de subclasse

No iOS e no Android, os seletores 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 subclasse.

iOS

A implementação do iOS subclasse a UIPickerView exibição e expõe propriedades e um evento que pode ser facilmente consumido do 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 selecionado 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 do 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 à 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 manipulador de OnBindableSpinnerItemSelected eventos atualiza a SelectedObject propriedade.