しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin.iOS で ViewModelLocator を使ってみる

前回 作成した Xamarin.Forms 用 ViewModelLocator をさっそく使ってみます

f:id:matatabi_ux:20141027061207p:plain

いつものように Xamarin.Forms の PCL プロジェクトを作成したら、前回作成した Xamarin.Forms 用 PCL をビルドしてできた Prism.Mvvm.Xamarin.dll を含む 3 つの DLL の参照を追加します

最終的に Nuget に登録するかもしれませんが、とりあえず InfrastructureAsemblies フォルダに配置して参照しています

そのあとかんたんな Page と ViewModel を追加します

<?xml version="1.0" encoding="utf-8" ?>
<local:AwareContentPage xmlns="http://xamarin.com/schemas/2014/forms"
      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
      xmlns:local="clr-namespace:PrismXamarin.Views;assembly=PrismXamarin"
      xmlns:prismmvvm="clr-namespace:Prism.Mvvm;assembly=Prism.Mvvm.Xamarin"
      prismmvvm:ViewModelLocator.AutoWireViewModel="true"
      x:Class="PrismXamarin.Views.TopPage">
  <Label Text="{Binding Message}" VerticalOptions="Center" HorizontalOptions="Center" />
</local:AwareContentPage>

これはトップ画面の XAML で、XAML に ViewModel を生成・参照するコードが記述されていないのがポイント

/// <summary>
/// トップ画面
/// </summary>
public partial class TopPage : AwareContentPage
{
    /// <summary>
    /// コンストラクタ
    /// </summary>
    public TopPage()
    {
        this.InitializeComponent();
    }
}

中身はからっぽ

/// <summary>
/// Prism 対応 ContentPage
/// </summary>
public class AwareContentPage : ContentPage, IView
{
    #region IView

    /// <summary>
    /// Bindable DataContext
    /// </summary>
    public object DataContext
    {
        get { return this.BindingContext; }
        set { this.BindingContext = value; }
    }

    #endregion //IView
}

AwareContentPage は ContentPage に IView のインタフェースを実装するためだけのクラスです

/// <summary>
/// トップ画面の ViewModel
/// </summary>
public class TopPageViewModel : ViewModelBase
{
    #region Message プロパティ

    /// <summary>
    /// Message
    /// </summary>
    private string message = string.Empty;

    /// <summary>
    /// Message の取得と設定
    /// </summary>
    public string Message
    {
        get { return this.message; }
        set { this.SetProperty<string>(ref this.message, value); }
    }

    #endregion //Message プロパティ

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public TopPageViewModel()
    {
        this.message = "Hello! Prism MVVM!";
    }
}

ViewModel はお決まりのアレ

あとは下準備を App.cs に追加!

/// <summary>
/// 共通 アプリケーションクラス
/// </summary>
public class App
{
    /// <summary>
    /// コンストラクタ
    /// </summary>
    static App()
    {
        // ViewModel をインスタンス化するデフォルトメソッドを指定します
        ViewModelLocationProvider.SetDefaultViewModelFactory((type) => Resolve(type));

        // View から対応する ViewModel を取得するロジックを設定します
        ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(viewType =>
        {
            var viewName = viewType.FullName;
            viewName = viewName.Replace(".Views.", ".ViewModels.");
            var viewAssemblyName = typeof(ViewModelBase).GetTypeInfo().Assembly.FullName;
            var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName);
            return Type.GetType(viewModelName);
        });
    }

    /// <summary>
    /// メイン画面を取得します
    /// </summary>
    /// <returns>メイン画面</returns>
    public static Page GetMainPage()
    {
        return new TopPage();
    }

    /// <summary>
    /// 指定したクラスをインスタンス化します
    /// </summary>
    /// <param name="type">クラスの型</param>
    /// <returns>インスタンス化した指定クラス</returns>
    protected static object Resolve(Type type)
    {
        return Activator.CreateInstance(type);
    }
}

Prism for Windows Runtime のように ViewModelLocator の初期化処理を App クラスに追加しました

// View から対応する ViewModel を取得するロジックを設定します
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(viewType =>
{
    var viewName = viewType.FullName;
    viewName = viewName.Replace(".Views.", ".ViewModels.");
    var viewAssemblyName = typeof(ViewModelBase).GetTypeInfo().Assembly.FullName;
    var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName);
    return Type.GetType(viewModelName);
});

この部分は View と ViewModel をひもづける処理を記述しています

~View というクラス名の View だったら、~ViewModel をひもづけるということをしています

さて、これで動くはずなので実行してみると

f:id:matatabi_ux:20141027064500p:plain

Prism.Mvvm.Xamarin.dll が見つからないと怒られました

そこで、Xamarin.iOS、Xamarin.WinPhone のプロジェクトにも Prism.Mvvm.Xamarin.dll の参照を追加して再実行

f:id:matatabi_ux:20141027064500p:plain

それでもだめ!あれ~?っと思い Windows Phone の方を実行してみると

f:id:matatabi_ux:20141027065026p:plain

こっちは問題なし・・・自動で ViewModel がバインドされて画面にメッセージが表示されました

なんで iOS はだめなのか・・・いろいろ考えたあげく思いついたのが、Xamarin.Forms の XAMLXAML であって XAML ではないということです

どういうことかというと、ViewModelLocator を TopPage.xaml 内でしか参照していないので、アプリケーションのバイナリに含まれなくなったのではないかと予想

そのため、App.cs のコンストラクタを次のように書き換えてみました

/// <summary>
/// コンストラクタ
/// </summary>
static App()
{
    // Prism.Mvvm.Xamarin アセンブリを読み込み可能にするおまじない
    var autoWired = ViewModelLocator.AutoWireViewModelProperty.DefaultValue;

    // ViewModel をインスタンス化するデフォルトメソッドを指定します
    ViewModelLocationProvider.SetDefaultViewModelFactory((type) => Resolve(type));

    // View から対応する ViewModel を取得するロジックを設定します
    ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(viewType =>
    {
        var viewName = viewType.FullName;
        viewName = viewName.Replace(".Views.", ".ViewModels.");
        var viewAssemblyName = typeof(ViewModelBase).GetTypeInfo().Assembly.FullName;
        var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName);
        return Type.GetType(viewModelName);
    });
}

ViewModelLocator を C# コード側から参照するおまじないを追加しただけ!

この状態で実行すると

f:id:matatabi_ux:20141027065716p:plain

動いた~!・・・やっぱり Xamarin.Forms の XAML は曲者ですね;