Exibições nativas em XAML

Baixar exemplo Baixar o exemplo

Exibições nativas do iOS, Android e do 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 xmlns de namespace no arquivo XAML para o namespace que contém a exibição nativa.
  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 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 em 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 Consulte 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, Tizenou WPF. Em runtime, o analisador XAML ignorará quaisquer prefixos de namespace XML que tenham um targetPlatform que não corresponda à plataforma na qual o aplicativo está em execução.

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 x:Static extensão de marcação e o ios namespace .

Propriedades associáveis e 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 definindo na exibição View.HorizontalOptions="Center" 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 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 exibições nativas.

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.

Associações nativas

A associaçã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 enviadas automaticamente 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 .

Propriedades de exibições nativas também podem usar associação de dados. O exemplo de código a seguir demonstra a associaçã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 um Entry cuja IsEnabled propriedade é associada à 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 comutador nativo para cada plataforma. Cada comutador nativo usa uma TwoWay associação para atualizar o valor da NativeSwitchPageViewModel.IsSwitchOn propriedade. Portanto, quando a opção está desativada, o Entry é desabilitado e, quando a opção está ativada, o Entry é habilitado. As capturas de tela a seguir mostram essa funcionalidade em cada plataforma:

Comutador Nativo DesabilitadoHabilitado

As associações bidirecionais têm suporte automático, desde que a propriedade nativa implemente INotifyPropertyChangedou dê suporte a Key-Value Observing (KVO) no iOS ou seja um 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 comutador nativo é alterado, a Binding classe é notificada de que o usuário alterou o valor da opção e o valor da NativeSwitchPageViewModel.IsSwitchOn propriedade é atualizado.

Passar argumentos para exibições nativas

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, métodos nativos de fábrica de exibição (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 uma nova 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 uma nova Typeface no Android. O Typeface nome e o estilo da família são especificados pelos argumentos de método que são filhos do x:Arguments atributo.

O FontFamily construtor é usado para definir a TextBlock.FontFamily propriedade como uma nova FontFamily no Plataforma Universal do Windows (UWP). O FontFamily nome é especificado pelo argumento de método que é um filho do x:Arguments atributo .

Observação

Os argumentos devem corresponder aos tipos exigidos pelo construtor ou pelo 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 exibições nativas:

Configurando fontes em exibições nativas

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

Consulte 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 o modo de exibição nativo seja um filho de um ContentView que especifica 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 da plataforma.
  2. Recupere a NativeViewWrapper.NativeElement propriedade e converta-a para o tipo de exibição nativo.

A API nativa pode 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 de XAML para diferentes plataformas podem ser filhos 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 ContentViewx: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 NativeViewWrapper.NativeElement seguida, a propriedade é acessada para recuperar a exibição nativa como seu tipo nativo. A API da exibição nativa é 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, o Plataforma Universal do Windows (UWP) usa um separadoRoutedEventHandler, 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, que dimensiona e gira o controle nativo contido no ContentView chamado contentViewTextParent. As capturas de tela a seguir demonstram isso ocorrendo em cada plataforma:

ContentView que contém um controle nativo

Exibiçõ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 é subclasse 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 PROJETO de Ativo Compartilhado (SAP) e cercadas por diretivas de compilação condicional ou colocadas em projetos específicos da plataforma e referenciadas 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 exibições nativas 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 associa à SubclassedNativeControlsPageViewModel.SelectedFruit propriedade . 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 um modo de exibição de seletor nativo para cada plataforma. Cada exibição nativa exibe a coleção de frutas associando sua ItemSource propriedade à SubclassedNativeControlsPageViewModel.Fruits coleção. Isso permite que o usuário escolha uma fruta, conforme mostrado nas seguintes capturas de tela:

Exibições nativas subclasse

No iOS e no Android, os seletores nativos usam métodos para configurar os controles. Portanto, esses seletores devem ser subclasses para expor propriedades para torná-los amigáveis com XAML. No Plataforma Universal do Windows (UWP), o ComboBox já é amigá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. Um UIPickerView requer um modelo de UIPickerViewModel dados subjacente, 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 for MyUIPickerView alterado, o Selected método será executado, o que atualiza o índice selecionado e aciona 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 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 no Adapter quando a ItemsSource propriedade é definida pela primeira vez. Sempre que o item selecionado na MySpinner classe for alterado, o OnBindableSpinnerItemSelected manipulador de eventos atualizará a SelectedObject propriedade .