Xamarin.iOSでMvvmCrossとStoryboardを使っている時にShowViewModelを動作させるためにやったこと

StoryboardとMvvmCrossをうまく同居させるときに色々苦労したのでここにメモります
Xamarin.Formsが出たのでちょっと旬が過ぎた気はしますが、需要はあるかなと

Storyboardを使っているときにMvvmCrossのShowViewModelが使えなかったので
ここではそれをどう直したかについてメモを書いていきます

やり方はMvxTouchViewPresentatorとMvxTouchViewsContainerの2つを継承し、既存のクラスを置き換えることで解決します。

* MvxTouchViewPresentatorを継承したクラスを作成し、RootViewControllerをMasterNavigationControllerに代入する

```
    public class CustomTouchViewPresenter : MvxTouchViewPresenter
    {

        public CustomTouchViewPresenter(UIApplicationDelegate applicationDelegate, UIWindow window) : base(applicationDelegate, window)
        {
            var uiNavigationController = window.RootViewController as UINavigationController;
            if (uiNavigationController != null)
                this.MasterNavigationController = uiNavigationController;
        }

    }
```

* MvxTouchViewControllerをstoryboard対応のために以下のように拡張する

```
    public class MvxStoryboardTouchViewsContainer : MvxTouchViewsContainer
    {
        const string ViewModelSuffix = "ViewModel";
        const string ControllerSuffix = "Controller";
        UIStoryboard _storyboard;
        public MvxStoryboardTouchViewsContainer(string storyboardName)
        {
            _storyboard = UIStoryboard.FromName(storyboardName, null);
        }
        public override IMvxTouchView CreateView(Cirrious.MvvmCross.ViewModels.MvxViewModelRequest request)
        {
            var view = CreateViewFromViewModelType(request.ViewModelType);
            if(view == null)
            {
                var viewType = GetViewType(request.ViewModelType);
                if (viewType == null)
                    throw new MvxException("View Type not found for " + request.ViewModelType);

                view = CreateViewOfType(viewType, request);
            }
            view.Request = request;
            return view;
        }
        protected override IMvxTouchView CreateViewOfType(Type viewType, Cirrious.MvvmCross.ViewModels.MvxViewModelRequest request)
        {
            return CreateView(viewType.Name);
        }

        IMvxTouchView CreateViewFromViewModelType(Type viewModelType)
        {
            var name = viewModelType.Name;
            if (name.EndsWith(ViewModelSuffix))
            {
                name = name.Substring(0, name.Length - ViewModelSuffix.Length) + ControllerSuffix;
            }
            return CreateView(name);
        }

        IMvxTouchView CreateView(string viewName)
        {

            return (IMvxTouchView)_storyboard.InstantiateViewController(viewName);
        }
    }

```

* SetupクラスとAppDelegateを以下のように置き換える

```
    public class Setup : MvxTouchSetup
    {
        public Setup(MvxApplicationDelegate appDelegate, IMvxTouchViewPresenter presenter)
            : base(appDelegate, presenter) { }

        protected override IMvxApplication CreateApp()
        {
            return new MyApp();
        }
        protected override Cirrious.MvvmCross.Touch.Views.IMvxTouchViewsContainer CreateTouchViewsContainer()
        {
            return new MvxStoryboardTouchViewsContainer("MainStoryboard");
        }

    }

	[Register ("AppDelegate")]
    public partial class AppDelegate : MvxApplicationDelegate
	{
		// class-level declarations
		public override UIWindow Window {
			get;
			set;
		}
		// This method is invoked when the application is about to move from active to inactive state.
		// OpenGL applications should use this method to pause.
		public override void OnResignActivation (UIApplication application)
		{
		}
		// This method should be used to release shared resources and it should store the application state.
		// If your application supports background exection this method is called instead of WillTerminate
		// when the user quits.
		public override void DidEnterBackground (UIApplication application)
		{
		}
		// This method is called as part of the transiton from background to active state.
		public override void WillEnterForeground (UIApplication application)
		{
		}
		// This method is called when the application is about to terminate. Save data, if needed.
		public override void WillTerminate (UIApplication application)
		{
		}

        public override void FinishedLaunching(UIApplication application)
        {
            var setup = new Setup(this, new CustomTouchViewPresenter(this, Window));
            setup.Initialize();
        }
	}

```

* Storyboard Idとクラス名を以下のように修正する

○○ViewModelに対応するControllerのStoryboard Idを○○Controllerに指定する

以上です。

動作原理
--------
* MvxTouchViewsContainerでControllerの解決を行っているのでStoryboardから取得するように修正。その際、ViewModelのクラス名からStoryboard Idを取得
* Storyboardだと、正しくNavigationControllerが認識されないので、Presentatorを継承してコンストラクタでRootViewControllerから取得して代入