XAML のネイティブ ビュー

iOS、Android、ユニバーサル Windows プラットフォームのネイティブ ビューは、Xamarin.Forms XAML ファイルから直接参照できます。 プロパティとイベント ハンドラーはネイティブ ビューで設定でき、Xamarin.Forms ビューと連携できます。 この記事では、Xamarin.Forms XAML ファイルからネイティブ ビューを使用する方法を見ていきます。

ネイティブ ビューを Xamarin.Forms XAML ファイルに埋め込むには:

  1. ネイティブ ビューを含む名前空間の XAML ファイルに、xmlns 名前空間の宣言を追加します。
  2. XAML ファイルでネイティブ ビューのインスタンスを作成します。

重要

ネイティブ ビューを使う XAML ページで、コンパイル済み XAML を無効にする必要があります。 これを行うには、XAML ページの分離コード クラスを [XamlCompilation(XamlCompilationOptions.Skip)] 属性で修飾します。 XAML のコンパイルについて詳しくは、「Xamarin.Forms での XAML のコンパイル」をご覧ください。

分離コード ファイルからネイティブ ビューを参照するには、共有アセット プロジェクト (SAP) を使い、プラットフォーム固有のコードを条件付きコンパイル ディレクティブでラップする必要があります。 詳しくは、「コードからネイティブ ビューを参照する」をご覧ください。

ネイティブ ビューを使用する

次のコード例では、Xamarin.Forms の ContentPage に各プラットフォームのネイティブ ビューを使用する方法を見ていきます。

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

ネイティブ ビュー名前空間の clr-namespaceassembly を指定するだけでなく、targetPlatform も指定する必要があります。 これは、iOSAndroidUWPWindows (UWP と同等)、macOSGTKTizen、または WPF に設定する必要があります。 実行時に、XAML パーサーは、アプリケーションが実行されているプラットフォームと一致しない targetPlatform を持つ XML 名前空間プレフィックスを無視します。

各名前空間宣言を使って、指定した名前空間の任意のクラスまたは構造体を参照できます。 たとえば、ios 名前空間宣言を使って、iOS の UIKit 名前空間の任意のクラスまたは構造体を参照できます。 ネイティブ ビューのプロパティは XAML を使って設定できますが、プロパティとオブジェクトの型が一致している必要があります。 たとえば、UILabel.TextColor プロパティは、x:Static マークアップ拡張と ios 名前空間を使って UIColor.Red に設定されます。

バインド可能プロパティとアタッチされたバインド可能プロパティは、Class.BindableProperty="value" 構文を使ってネイティブ ビューで設定することもできます。 各ネイティブ ビューは、Xamarin.Forms.View クラスから派生したプラットフォーム固有の NativeViewWrapper インスタンスにラップされます。 ネイティブ ビューでバインド可能プロパティまたはアタッチされたバインド可能プロパティを設定すると、プロパティ値がラッパーに転送されます。 たとえば、ネイティブ ビューで View.HorizontalOptions="Center" を設定して、中央揃えの水平レイアウトを指定できます。

Note

スタイルで対象にできるのは BindableProperty オブジェクトによってサポートされるプロパティだけなので、ネイティブ ビューではスタイルを使用できないことに注意してください。

Android ウィジェット コンストラクターでは、通常、引数として Android Context オブジェクトが必要であり、これは MainActivity クラスの静的プロパティを介して使用できます。 そのため、XAML で Android ウィジェットを作成するときは、通常、x:Arguments 属性と x:Static マークアップ拡張を使って、Context オブジェクトをウィジェットのコンストラクターに渡す必要があります。 詳しくは、「ネイティブ ビューに引数を渡す」をご覧ください。

Note

.NET Standard ライブラリ プロジェクトまたは共有アセット プロジェクト (SAP) では、x:Name を使ってネイティブ ビューの名前を指定できないことに注意してください。 それを行うと、ネイティブ型の変数が生成され、コンパイル エラーが発生します。 ただし、SAP が使われている場合は、ネイティブ ビューを ContentView インスタンスにラップして、分離コード ファイルで取得できます。 詳しくは、「コードからネイティブ ビューを参照する」をご覧ください。

ネイティブ バインド

データ バインディングは、UI とそのデータ ソースを同期するために使われ、Xamarin.Forms アプリケーションによるデータの表示と操作の方法を簡単にします。 ソース オブジェクトで INotifyPropertyChanged インターフェイスが実装されていると、"ソース" オブジェクトでの変更はバインディング フレームワークによって "ターゲット" オブジェクトに自動的にプッシュされ、"ターゲット" オブジェクトでの変更は、必要に応じて、"ソース" オブジェクトにプッシュできます。

ネイティブ ビューのプロパティでも、データ バインディングを使用できます。 次のコード例では、ネイティブ ビューのプロパティを使用するデータ バインディングについて見ていきます。

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

このページに含まれる EntryIsEnabled プロパティは、NativeSwitchPageViewModel.IsSwitchOn プロパティにバインドしています。 ページの BindingContext は、分離コード ファイルで NativeSwitchPageViewModel クラスの新しいインスタンスに設定されており、ViewModel クラスは INotifyPropertyChanged インターフェイスを実装しています。

このページには、各プラットフォームのネイティブ スイッチも含まれています。 各ネイティブ スイッチは、TwoWay バインドを使って、NativeSwitchPageViewModel.IsSwitchOn プロパティの値を更新します。 そのため、スイッチがオフのときは Entry は無効であり、スイッチがオンのときは Entry は有効です。 次のスクリーンショットは、各プラットフォームでのこの機能を示したものです。

ネイティブ スイッチが無効ネイティブ スイッチが有効

ネイティブ プロパティが、INotifyPropertyChanged を実装している場合、または iOS でキー値監視 (KVO) をサポートしている場合、または UWP で DependencyProperty である場合は、両方向のバインドが自動的にサポートされます。 ただし、多くのネイティブ ビューでは、プロパティ変更通知はサポートされていません。 これらのビューでは、バインド式の一部として UpdateSourceEventName プロパティの値を指定できます。 このプロパティには、ターゲット プロパティが変化したときに通知するネイティブ ビューのイベントの名前を設定する必要があります。 その後は、ネイティブ スイッチの値が変化すると、ユーザーがスイッチの値を変更したことが Binding クラスに通知されて、NativeSwitchPageViewModel.IsSwitchOn プロパティの値が更新されます。

ネイティブ ビューに引数を渡す

x:Arguments 属性と x:Static マークアップ拡張を使って、コンストラクター引数をネイティブ ビューに渡すことができます。 さらに、x:FactoryMethod 属性を使ってメソッドの名前を指定し、x:Arguments 属性を使ってその引数を指定すると、ネイティブ ビュー ファクトリ メソッド (メソッドが定義されているクラスまたは構造体と同じ型のオブジェクトまたは値を返す public static メソッド) を 呼び出すことができます。

次のコード例では、両方の手法を見ていきます。

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

UIFont.FromName ファクトリ メソッドは、iOS で、UILabel.Font プロパティを新しい UIFont に設定するために使われます。 UIFont の名前とサイズは、x:Arguments 属性の子であるメソッド引数で指定します。

Typeface.Create ファクトリ メソッドは、Android で、TextView.Typeface プロパティを新しい Typeface に設定するために使われます。 Typeface ファミリの名前とスタイルは、x:Arguments 属性の子であるメソッド引数で指定します。

FontFamily コンストラクターは、ユニバーサル Windows プラットフォーム (UWP) で、TextBlock.FontFamily プロパティを新しい FontFamily に設定するために使われます。 FontFamily の名前は、x:Arguments 属性の子であるメソッド引数で指定します。

Note

引数は、コンストラクターまたはファクトリ メソッドで必要な型と一致する必要があります。

次のスクリーンショットは、ファクトリ メソッドとコンストラクターの引数を指定して、異なるネイティブ ビューでフォントを設定した結果を示したものです。

ネイティブ ビューでフォントを設定する

XAML で引数を渡す方法について詳しくは、「XAML での引数の受け渡し」をご覧ください。

コードからネイティブ ビューを参照する

x:Name 属性でネイティブ ビューの名前を指定することはできませんが、ネイティブ ビューが x:Name 属性値を指定する ContentView の子である場合は、XAML ファイルで宣言されているネイティブ ビュー インスタンスを、共有アクセス プロジェクトの分離コード ファイルから取得できます。 その後、分離コード ファイルの条件付きコンパイル ディレクティブ内で、次のようにする必要があります。

  1. ContentView.Content プロパティの値を取得して、プラットフォーム固有の NativeViewWrapper 型にキャストします。
  2. NativeViewWrapper.NativeElement プロパティを取得して、ネイティブ ビュー型にキャストします。

その後、ネイティブ ビューでネイティブ API を呼び出して、目的の操作を実行できます。 この方法では、異なるプラットフォームの複数の XAML ネイティブ ビューを同じ ContentView の子にできるという利点もあります。 次のコード例は、この手法を示したものです。

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

上の例で、各プラットフォームのネイティブ ビューは ContentView コントロールの子であり、コードビハインドで ContentView を取得するには x:Name 属性の値が使われています。

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

ContentView.Content プロパティにアクセスして、ラップされたネイティブ ビューをプラットフォーム固有の NativeViewWrapper インスタンスとして取得します。 その後、NativeViewWrapper.NativeElement プロパティにアクセスして、ネイティブ ビューをネイティブ型として取得します。 それから、ネイティブ ビューの API を呼び出して、必要な操作を実行します。

各ネイティブ ボタンはタッチ イベントへの応答で EventHandler デリゲートを使うため、iOS と Android のネイティブ ボタンは同じ OnButtonTap イベント ハンドラーを共有します。 一方、ユニバーサル Windows プラットフォーム (UWP) は別の RoutedEventHandler を使い、それによって、この例では、OnButtonTap イベント ハンドラーが使われます。 したがって、ネイティブ ボタンがクリックされると、OnButtonTap イベント ハンドラーが実行して、contentViewTextParent という名前の ContentView に含まれるネイティブ コントロールの拡大縮小と回転を行います。 次のスクリーンショットは、各プラットフォームでこれが行われているのを示したものです。

ネイティブ コントロールを含む ContentView

ネイティブ ビューをサブクラス化する

iOS と Android の多くのネイティブ ビューは、コントロールを設定するためにプロパティではなくメソッドを使っているため、XAML でのインスタンス化に適していません。 この問題の解決策は、プロパティを使ってコントロールを設定し、プラットフォームに依存しないイベントを使用する、いっそう XAML で使いやすい API が定義されているラッパーで、ネイティブ ビューをサブクラス化することです。 その後は、ラップされたネイティブ ビューを、共有アセット プロジェクト (SAP) に配置して条件付きコンパイル ディレクティブで囲んだり、プラットフォーム固有のプロジェクトに配置して、.NET Standard ライブラリ プロジェクトの XAML から参照したりできます。

次のコード例では、サブクラス化されたネイティブ ビューを使用する Xamarin.Forms ページを見ていきます。

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

このページには、ユーザーがネイティブ コントロールで選んだフルーツを表示する Label が含まれます。 Label は、SubclassedNativeControlsPageViewModel.SelectedFruit プロパティにバインドしています。 ページの BindingContext は、分離コード ファイルで SubclassedNativeControlsPageViewModel クラスの新しいインスタンスに設定されており、ViewModel クラスは INotifyPropertyChanged インターフェイスを実装しています。

このページには、各プラットフォームのネイティブ ピッカー ビューも含まれています。 各ネイティブ ビューでは、SubclassedNativeControlsPageViewModel.Fruits コレクションにバインドされた ItemSource プロパティを使って、果物のコレクションを表示します。 これにより、次のスクリーンショットで示すように、ユーザーは果物を選ぶことができます。

サブクラス化されたネイティブ ビュー

iOS と Android のネイティブ ピッカーは、メソッドを使ってコントロールを設定します。 そのため、これらのピッカーは、XAML に対応するように、サブクラス化してプロパティを公開する必要があります。 ユニバーサル Windows プラットフォーム (UWP) では、ComboBox は既に XAML に対応しているため、サブクラス化する必要はありません。

iOS

iOS の実装では、UIPickerView ビューがサブクラス化されて、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 { }
    }
}

MyUIPickerView クラスは、ItemsSourceSelectedItem プロパティ、および SelectedItemChanged イベントを公開します。 UIPickerView には基になる UIPickerViewModel データ モデルが必要であり、それは MyUIPickerView のプロパティとイベントによってアクセスされます。 UIPickerViewModel データ モデルは、PickerModel クラスによって提供されます。

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

PickerModel クラスは、Items プロパティを介して、MyUIPickerView クラスに基になるストレージを提供します。 MyUIPickerView で選択項目が変化するたびに、Selected メソッドが実行され、それにより選ばれたインデックスが更新されて、ItemChanged イベントが生成されます。 これにより、SelectedItem プロパティは常に、ユーザーが選んだ最後の項目を返します。 さらに、PickerModel クラスは、MyUIPickerView インスタンスのセットアップに使われるメソッドをオーバーライドします。

Android

Android の実装では、Spinner ビューがサブクラス化されて、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);
    }
}

MySpinner クラスは、ItemsSourceSelectedObject プロパティ、および ItemSelected イベントを公開します。 MySpinner クラスによって表示される項目は、ビューに関連付けられた Adapter によって提供され、ItemsSource プロパティが最初に設定されたときに、項目が Adapter に設定されます。 MySpinner クラスで選択項目が変わるたびに、OnBindableSpinnerItemSelected イベント ハンドラーによって SelectedObject プロパティが更新されます。