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から取得して代入