MvvmCross TipCalc を Xamarin 5.1.4 で動かしてみる
TipCalc.Core の作成
空のソリューションに PCLプロジェクト追加
MvvmCross Hot Tuna Starter Pack
追加前
TipCalc.Core/
├── MyClass.cs
├── Properties
│ └── AssemblyInfo.cs
├── TipCalc.Core.csproj
├── bin
│ └── Debug
└── obj
└── Debug
追加後
TipCalc.Core/
├── App.cs
├── MyClass.cs
├── Properties
│ └── AssemblyInfo.cs
├── TipCalc.Core.csproj
├── ToDo-MvvmCross
│ └── _\ Core.txt
├── ViewModels
│ └── FirstViewModel.cs
├── bin
│ └── Debug
├── obj
│ └── Debug
└── packages.config
Service(Model)インターフェースとクラス実装
- Serviceフォルダを追加
ICalculationインターフェース定義
using System;
namespace TipCalc.Core
{
public interface ICalculation
{
double TipAmount(double subTotal, int generosity);
}
}
Caluculationクラス定義
using System;
namespace TipCalc.Core
{
public class Calculation : ICalculation
{
public double TipAmount(double subTotal, int generosity)
{
return subTotal * ((double)generosity)/100.0;
}
}
}
ViewModelの実装
- ViewModel == UI機能がないアプリケーション
- ViewModels/FirstViewModel.cs が追加されているのでこれをリファクタリング
- Start()で開始
- 変数はPropertyでアクセス可能
- 変更通知が必要が変数は、setの中でRecalcuate()でUIに通知する
こんな
using Cirrious.MvvmCross.ViewModels;
namespace TipCalc.Core.ViewModels
{
public class TipViewModel
: MvxViewModel
{
/// <summary>
/// ICalculation Container
/// </summary>
private readonly ICalculation _calculation;
public TipViewModel(ICalculation calculation)
{
_calculation = calculation;
}
/// <summary>
/// Start this instance with some initial values.
/// </summary>
public override void Start()
{
_subTotal = 100;
_generosity = 10;
Recalcuate();
base.Start();
}
/// <summary>
/// Recalcuate the Tip
/// </summary>
private void Recalcuate()
{
Tip = _calculation.TipAmount(SubTotal, Generosity);
}
// Values are exposed thru "Property"
// If a Property value was changed,
// a event is fired thru "RaisePropertyChange".
private double _subTotal;
public double SubTotal
{
get { return _subTotal; }
set {
_subTotal = value;
RaisePropertyChanged(() => SubTotal);
Recalcuate();
}
}
private int _generosity;
public int Generosity
{
get { return _generosity; }
set {
_generosity = value;
RaisePropertyChanged(() => Generosity);
Recalcuate();
}
}
private double _tip;
public double Tip
{
get { return _tip; }
set {
_tip = value;
RaisePropertyChanged(() => Tip);
}
}
}
}
App の実装
- App.csが追加されているので、これを編集
こんな
using Cirrious.CrossCore.IoC;
using Cirrious.CrossCore;
namespace TipCalc.Core
{
public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
public override void Initialize()
{
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
RegisterAppStart<ViewModels.TipViewModel>();
// ICaliculationのクラスを登録
// TipViewModelのコンストラクタに必要なクラスを登録することで
// 自動的にクラスを見つけてコンストラクタに渡してくれる
Mvx.RegisterType<TipCalc.Core.ICalculation,TipCalc.Core.Calculation> ();
}
}
}
TipCalc.iOS の作成
iPhone Single View Application プロジェクト作成
MvvmCross Hot Tuna Starter Pack
- TipCalc.Coreと同様
追加前
TipCalc.iOS/
├── AppDelegate.cs
├── Entitlements.plist
├── Info.plist
├── Main.cs
├── MainStoryboard.storyboard
├── Resources
│ └── Default-568h@2x.png
├── TipCalc.iOS.csproj
├── TipCalc.iOSViewController.cs
└── TipCalc.iOSViewController.designer.cs
ファイルが追加
TipCalc.iOS/
├── AppDelegate.cs
├── AppDelegate.cs.txt
├── DebugTrace.cs
├── Entitlements.plist
├── Info.plist
├── LinkerPleaseInclude.cs
├── Main.cs
├── MainStoryboard.storyboard
├── Resources
│ └── Default-568h@2x.png
├── Setup.cs
├── TipCalc.iOS.csproj
├── TipCalc.iOSViewController.cs
├── TipCalc.iOSViewController.designer.cs
├── ToDo-MvvmCross
│ └── _\ Touch\ UI.txt
├── Views
│ └── FirstView.cs
└── packages.config
StoryboardでUIを設定(Outlet生成)
保存するとOutletがUIViewControllerのコードビハインドに生成されます。
namespace TipCalc.iOS
{
[Register ("TipCalc_iOSViewController")]
partial class TipCalc_iOSViewController
{
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UISlider Generosity { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UITextField SubTotal { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UILabel Tip { get; set; }
}
UIViewControllerを修正してViewModelの通知を受け取る
- ベースクラスを UIViewController -> MvxViewController
- ViewDidLoadでOutletにViewModelの通知を反映させるように紐づける
using System;
using System.Drawing;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using TipCalc.Core.ViewModels;
using Cirrious.MvvmCross.Binding.BindingContext;
using Cirrious.MvvmCross.Touch.Views;
using Cirrious.MvvmCross.ViewModels;
namespace TipCalc.iOS
{
public partial class TipCalc_iOSViewController : MvxViewController
{
public TipCalc_iOSViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad ()
{
this.Request = new MvxViewModelRequest<TipViewModel>(
null, null, new MvxRequestedBy());
base.ViewDidLoad();
// ViewModelのデータ変更通知を受け取るので表示が変わる
this.CreateBinding(this.Generosity).To<TipViewModel>(vm => vm.Generosity).Apply();
this.CreateBinding(this.Tip).To<TipViewModel>(vm => vm.Tip).Apply();
this.CreateBinding(this.SubTotal).To<TipViewModel>(vm => vm.SubTotal).Apply();
// Perform any additional setup after loading the view, typically from a nib.
}
}
}
Setup クラスを追加して、ViewModelの設定を行う
- Tot Tunaが生成してくれる
using MonoTouch.UIKit;
using Cirrious.CrossCore;
using Cirrious.CrossCore.Platform;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Touch.Platform;
namespace TipCalc.iOS
{
public class Setup : MvxTouchSetup
{
public Setup(MvxApplicationDelegate applicationDelegate, UIWindow window)
: base(applicationDelegate, window)
{
}
protected override IMvxApplication CreateApp ()
{
return new Core.App();
}
protected override IMvxTrace CreateDebugTrace()
{
return new DebugTrace();
}
}
}
AppDelegateを修正してSetupの実行
* ベースクラスを UIApplicationDelegate -> MvxApplicationDelegate
- FinishedLaunchingをオーバーライドしてSetup実行
using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using TipCalc.Core.ViewModels;
using Cirrious.CrossCore;
using Cirrious.MvvmCross.Binding.BindingContext;
using Cirrious.MvvmCross.Touch.Views;
using Cirrious.MvvmCross.Touch.Platform;
using Cirrious.MvvmCross.ViewModels;
namespace TipCalc.iOS
{
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register ("AppDelegate")]
public partial class AppDelegate : MvxApplicationDelegate
{
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
//base.FinishedLaunching を呼び出してはいけない。
var setup = new Setup(this, this.Window);
setup.Initialize();
return true;
}
}
}
モデル/サービスを外部から与える
- 例えば、ICalculationの実装が別のPCLに定義されているとか
- iOS UIアプリ側から与えたい場合は、Setupを修正
InitializeFirstChanceでコンストラクタに使われるタイプを登録する:
using MonoTouch.UIKit;
using Cirrious.CrossCore;
using Cirrious.CrossCore.Platform;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Touch.Platform;
namespace TipCalc.iOS
{
public class Setup : MvxTouchSetup
{
public Setup(MvxApplicationDelegate applicationDelegate, UIWindow window)
: base(applicationDelegate, window)
{
}
protected override void InitializeFirstChance ()
{
base.InitializeFirstChance ();
Mvx.RegisterType<TipCalc.Core.ICalculation,TipCalc.Core.Calculation> ();
}
}
}