ぴーさんログ

だいたいXamarin.Formsのブログ

【Xamarin.Forms 2.3.3 -pre2】XAML内でのネイティブView定義とBindingのサポート

Xamarin.Forms 2.3.3 -pre2でXAML内でネイティブプラットフォーム(iOSAndroid、UWP)のコントロールを配置できるようになります。 これはXamarin.Forms 2.2で追加されたNative Embeddingという機能の発展系であると言えます。

ticktack.hatenablog.jp

Native Embedding - Xamarin

おさらい

まずはXF 2.2で追加された時点のNative Embeddingがどんな物だったか確認しましょう。 使用時のコードはこんな感じです。

            // CotentPageのコンストラクタの中
#if __IOS__
            var uiLabel = new UILabel {
                MinimumFontSize = 14f,
                Lines = 0,
                LineBreakMode = UILineBreakMode.WordWrap,
                Text = text + "(iOS)",
            };
            Content = uiLabel.ToView ();
#elif __ANDROID__
            var textView = new TextView(Forms.Context) {
                Text = text + "(Android)",
                TextSize = 14,
            };
            Content = textView.ToView ();
#endif

Sharedプロジェクト内にifディレクティブでプラットフォーム固有のコードを定義します。 ネイティブプラットフォームのコントロールを(ToViewなどの)拡張メソッドでXamarin.FormsのViewに変換してXamarin.Formsのレイアウトの中に組み込んでいます。 UIKitなどのプラットフォーム固有ライブラリへの参照が発生するため、PCLでは使用する事ができずSharedプロジェクト専用となっていました。

XF 2.3.3では

こんなXAMLが動くようになります。

XAML

<?xml version="1.0" encoding="utf-8" ?>
<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:formsandroid="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.Platform.Android;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="XFApp38.XFApp38Page">
    <ContentPage.Content>
        <StackLayout VerticalOptions="Center">
            <!-- iOSのコントロール -->
            <ios:UITextView Text="{Binding NativeText, Mode=TwoWay, UpdateSourceEventName=Ended}"
                View.BackgroundColor="{Binding Color}" />
            <ios:UILabel Text="{Binding NativeText}" View.BackgroundColor="Lime" />
            
            <!-- Androidのコントロール -->
            <androidWidget:TextView Text="{Binding NativeText}" x:Arguments="{x:Static formsandroid:Forms.Context}"/>
            
            <!-- UWPのコントロール -->
            <win:TextBlock Text="This is TextBlock"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

コードビハインド

using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;

namespace XFApp38
{
    public partial class XFApp38Page : ContentPage
    {
        public XFApp38Page ()
        {
            BindingContext = new ViewModel ();
            InitializeComponent ();
        }
    }

    public class ViewModel : INotifyPropertyChanged
    {
        private string _NativeText = "Hoge text";
        public string NativeText {
            get { return _NativeText; }
            set {
                if (_NativeText == value)
                    return;

                _NativeText = value;
                RaisePropertyChanged ();
            }
        }

        private Color _Color = Color.FromHex("#AAAAAA");
        public Color Color {
            get { return _Color; }
            set {
                if (_Color == value)
                    return;

                _Color = value;
                RaisePropertyChanged ();
            }
        }

       #region INPC
        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged ([CallerMemberName]string propertyName = "")
        {
            PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
        }
       #endregion
    }
}

StackLayoutの中にiOSAndroid、UWP固有のコントロールが配置されています。

ネイティブコントロールの配置方法

Xamarin.Forms用のカスタムコントロールを配置する場合と同じように、xmlnsを宣言して対象クラスが定義されているassemblyを参照します。 通常と異なる点は targetPlatform=〜〜 を追加すること、これにより実行時のプラットフォームと異なるものを無視することができます。 (おおむねC#でのifディレクティブに相当するものと考えて良いでしょう)

ネイティブコントロールのプロパティへのBinding

ネイティブコントロールに対してもBindingを使うことができ、可能ならば2Way Bindingもサポートされます。 変更通知機構を持たないプロパティに対しては、Binding構文に追加された UpdateSourceEventName パラメータでトリガーを指定できます。 ("ios:UITextView"の部分で使用しています)

Xamarin Forms Viewのプロパティへのアクセス

ネイティブコントロールをラップするViewのパラメータに値やBindingをセットすることも可能です。("View.BackgroundColor=〜〜"の部分) ラッパーである NativeViewWrapper クラスは View のサブクラスなので、指定できるプロパティはViewと同じと考えれば良いでしょう。

ここがスゴイ

何と言ってもPCLでもプラットフォーム固有のコントロールを埋め込めることが素晴らしい! XAMLは明示的に指定しない限りテキストファイルとしてアプリに埋め込まれ、実行時に解釈されます。なので動作プラットフォーム以外のコントロールを無視すればエラーにならないんですね。いやー画期的。

さらにUpdateSourceEventNameを利用した任意イベントでの2Way Bindingまで用意されていて隙がありません。

制限事項

XamlC(XAMLの事前コンパイル)との併用ができません。

コンパイル時にプラットフォーム固有クラスへの参照が発生してしまうからでしょうね。 Sharedプロジェクトならばビルドターゲット以外のプラットフォーム部分を削った上でXAMLコンパイルできそうですが...

PCLの場合は....、参照しているプラットフォーム固有クラスの空実装dllを生成して、実行時に本物と差し代わるようにしてやればイケるでしょうか.....

UWPコントロールに対してBindingを設定するとエラーになります。サンプルコードでもUWPのTextBlockだけはリテラルだったので現時点では未対応なのかも?