Xamarin.Forms for MacがStoryboardと仲良くする方法

この記事は [[初心者さん・学生さん大歓迎！] Xamarin その1 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/xamarin-student) の16日目の記事です。

## 題について

勢いでサクッとつけてしまった題ですが、思ってたのとちょっと違うところです。
実際には、「 **他プラットフォームとの互換性を最大限に維持しつつ** Xamarin.Forms for MacがStoryboardと仲良くする方法」ですね。

## もっと標準コンロールで

Storyboardでは

![101.png](https://qiita-image-store.s3.amazonaws.com/0/59270/96b32732-65ec-b0d1-0164-d976a6c438b4.png)

こういうコントロールを出すのは楽なのに。
（もちろん、こいつだとsegue一発なんでXamarin.Formsでもコード書きゃ終いですが）
でもDataBindingやら他機種へのポーティングを考えるとXamarin.Formsのコード使えたら楽なのに。

というわけで、いいとこ取りする簡単な方法です。

## 実現方法

### 理屈：Xamarin.Forms

Xamarin.Forms for Macの動きとして。

* Forms.Init()は適当な時に呼び出しても良い
* Forms.Init()はFormsApplicationDelegate.LoadApplicationの前に呼びださなければいけない
* ApplicationのMainPageは設定されていなくても良い
* ApplicationのMainPageが設定されていなければ、FormsApplicationDelegate.Windowは設定されていなくても良い
* FormsApplicationDelegate.LoadApplicationは最初のPage操作の前に呼び出されていなければいけない
* Xamarin.FormsのPageからNSViewControllerを作れる

### 理屈：Storyboard

また、Storyboard起動からの呼び出し順は、

1. FormsApplicationDelegate.&lt;&lt;constructor&gt;&gt;
2. NSViewController.ViewDidLoad
3. FormsApplicationDelegate.DidFinishLaunching

です。

### 理屈：統合

なので、本来ならFormsApplicationDelegate.DidFinishLaunchingでXamarin.Formsの処理を行いますが、その流れを変えて。

1. アプリ起動、UI始動までStoryboardで行う
2. Forms.Init()とLoadApplicationはFormsApplicationDelegate.&lt;&lt;constructor&gt;&gt;で行う
3. Application.MainPageを設定しない
4. FormsApplicationDelegate.Windowも設定しない
5. 表示関連はPageで用意しておき、ViewControllerにくっつける
6. くっつけるのはNSViewController.ViewDidLoadで行う
7. データの保持、更新処理はXamarinのBindingObjectに納める
8. BindingObjectからのデータ更新はPage側で受け、表示する

という処理にします。

## 作ってみた

[Github](https://github.com/yakumo/XamarinHelloWorldTest)に作ったものを置いてみました[^1]。

### [AppDelegate] (https://github.com/yakumo/XamarinHelloWorldTest/blob/master/src/mac/HelloWorldTest/AppDelegate.cs) : Storyboard

何もしないXamarin.Forms.Applicationのサブクラス（App）を作り、それをLoadApplicationで設定しています。
これで、StoryboardのDelegateとして読み込まれ、Xamarin.Forms的には初期化を行い、WindowはStoryboardのものを使うように設定しています。

他プラットフォームでは、LoadApplicationで設定しているのは[HelloWorldApp](https://github.com/yakumo/XamarinHelloWorldTest/blob/master/src/common/HelloWorldApp.cs)です。

### [MainData](https://github.com/yakumo/XamarinHelloWorldTest/blob/master/src/common/MainData.cs) : Xamarin.Forms

埋め込みリソースとして登録してあるJSONを読み込み、本棚データとして保持します[^2]。

### [TitlesViewController](https://github.com/yakumo/XamarinHelloWorldTest/blob/master/src/mac/HelloWorldTest/TitlesViewController.cs) : Storyboard

StoryboardでメインウインドウのViewControllerとして設定されます。
ViewDidLoadでTitleListPageを子ViewControllerとして設定しています。

### [TitleListPage](https://github.com/yakumo/XamarinHelloWorldTest/blob/master/src/common/TitleListPage.cs) : Xamarin.Forms

MainDataをBindingContextとしてわりつけ、表示している本棚の中の本の一覧を表示します。
他プラットフォームだと、MasterDetailPageのDetailにわりつけてメインのページになっています。

### [BookshelvesViewController](https://github.com/yakumo/XamarinHelloWorldTest/blob/master/src/mac/HelloWorldTest/BookshelvesViewController.cs) : Storyboard

ウインドウにわりつけられたToolBarのボタンからPopupする本棚の一覧を表示するViewControllerです。
ボタン押下でPopupまではStoryboardで設定されています。
ViewDidLoadでBookshelfListPageを子ViewControllerとして設定しています。

### [BookshelfListPage](https://github.com/yakumo/XamarinHelloWorldTest/blob/master/src/common/BookshelfListPage.cs) : Xamarin.Forms

MainDataをBindingContextとしてわりつけ、本棚一覧を表示します。
ポップアップの中身です。
他プラットフォームだと、MasterDetailPageのMasterにわりつけてます。

## 結論

意外に簡単にStoryboardと統合できました。
このやり方は、 `Page.CreateViewController` におんぶにだっこしてもらって実現できています[^3]。
MVVMで分けるのは微妙ではありますが、入力する量も少なくていいところではないかと思います。

[^1]: アイコンは[ICOOON MONO](http://icooon-mono.com/)さんのものを使用しています。
[^2]: 作った後に気づきましたが、コンストラクタはprivateの方が良いですねぃ。
[^3]: iOSでの同じやり方は意外に難しそうです。ListViewェ…。