Xamarin.Mac開発でStoryboardを初めて触る人に贈る、Xamarin公式 Working with Storyboards の日本語訳記事

[Xamarin.Macを初めて触る人に贈る、Xamarin公式 Hello, Mac の日本語訳記事](http://qiita.com/toshi0607/items/34b3e04f9ad24a5d0153)に引き続き。

Xamarin.Mac を開発するときに使う Storyboard やその中で扱う View Controller、 Segue、 Window Controller に馴染みがなかったので公式記事で勉強するついでに日本語訳しました。

この記事の内容と一番下で紹介されている [SourceWriter Sample App](https://developer.xamarin.com/samples/mac/SourceWriter/) を追えば多少キャッチアップできるかなぁと思っています。


# Storyboard
Storyboard は、特定のアプリのすべての UI を View Controller の機能概要に分けて定義します。 Xcode の Interface Builder では、これらのコントローラーはそれぞれ独自の Scene に存在します。
<img width="513" alt="スクリーンショット 2017-08-17 23.00.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/e8ea6487-dd22-46bb-e6a6-d2ee46796725.png">

Storyboard　は、Xamarin.Mac アプリケーションのバンドルがコンパイルされて送り出されるときに含まれるリソースファイル（拡張子は `.storyboard` ）です。アプリケーションの最初の Storyboard を定義するには `Info.plist` ファイルを編集し、ドロップダウンボックスから **Main Interface** を選択します。

<img width="519" alt="スクリーンショット 2017-08-17 23.08.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/b10c7ea1-d2aa-be24-d8b4-fe9dc7296599.png">

## コードからのロード
コードから特定の Storyboard をロードし、View Controller を手動で作成する必要がある場合があります。次のコードでそれが可能です。

```csharp
// Get new window
var storyboard = NSStoryboard.FromName ("Main", null);
var controller = storyboard.InstantiateControllerWithIdentifier ("MainWindow") as NSWindowController;

// Display
controller.ShowWindow(this);
```

`FromName` は、アプリケーションのバンドルに含まれている特定の名前の `Storyboard` ファイルを読み込みます。`InstantiateControllerWithIdentifier` は、指定された ID を持つ View Controller のインスタンスを生成します。UI をデザインするときに、Xcode の Interface Builder で ID を設定します。

<img width="555" alt="スクリーンショット 2017-08-17 23.18.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/aa5c2ab4-bd8a-b28b-690f-60ce6881db9f.png">

必要に応じて、`InstantiateInitialController` メソッドを使用して、Interface Builder で最初の Controller が割り当てられた View Controller をロードすることができます。

<img width="562" alt="スクリーンショット 2017-08-17 23.20.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/3666d4f2-98dc-a92c-88db-a1761a4ab56c.png">

**Storyboard Entry Point** と矢印でマークされています。

# View Controller
View Controller は、Mac アプリケーション内の特定の View の情報とその情報を提供するデータモデルとの関係を定義します。Storyboard　の各トップレベル scene は、Xamarin.Mac アプリのコード内の1つの View Controller を表します。

## View Controller のライフサイクル
macOS で Storyboard をサポートするために `NSViewController` クラスにいくつかの新しいメソッドが追加されました。最も重要なのは、特定の View Controllerによって制御されている View のライフサイクルに応答するために使用される以下のメソッドです。

* `ViewDidLoad ` - このメソッドは、ビューが Storyboard ファイルからロードされたときに呼び出されます。
* `ViewWillAppear` - このメソッドは、ビューが画面に表示される直前に呼び出されます。
* `ViewDidAppear ` - このメソッドは、ビューが画面に表示された直後に呼び出されます。
* `ViewWillDisappear` - このメソッドは、ビューが画面から削除される直前に呼び出されます。
* `ViewDidDisappear` - このメソッドは、ビューが画面から削除された直後に呼び出されます。
* `UpdateViewConstraints ` - このメソッドは、ビューの自動レイアウトの位置とサイズを定義する制約が更新される必要があるときに呼び出されます。
* `ViewWillLayout` - このメソッドは、このビューのサブビューが画面上に配置される直前に呼び出されます。
* `ViewDidLayout ` - このメソッドは、ビューのサブビューが画面上に配置された直後に呼び出されます。

## Responder Chain
さらに、`NSViewController` は ウィンドウの *Responder Chain* の一部になりました。

<img width="560" alt="スクリーンショット 2017-08-17 23.41.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/bccfc5c0-9795-55c1-7747-19d1432de610.png">

また、カット、コピー、ペーストのメニュー項目の選択などのイベントを受けて​​応答するために繋がれています。この自動の View Controller 配線は、Mac OS X Yosemite　（10.10）以上で動作するアプリでのみ発生します。

## Containment
Storyboard では、View Controller （ Split View Controller や Tab View Controller など）は、他のサブビューコントローラを「含む」ように、 *Containment* を実装できます。

<img width="508" alt="スクリーンショット 2017-08-17 23.48.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/b5633135-56d3-ebc9-7f50-4ad4a65bf59e.png">

子 View Controller には、それらを親 View Controller に結びつけ、画面から View を表示または削除するためのメソッドとプロパティが含まれています。

macOSに組み込まれているすべての Container View Controller には、独自のカスタム Container View Controller を作成する場合に Apple が推奨する特定のレイアウトがあります。

<img width="559" alt="スクリーンショット 2017-08-17 23.54.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/854ccfe1-e6ed-7925-4cbe-75f6feffaab0.png">

Collection View Controller には、それぞれ独自の View を含む1つまたは複数の View Controller が含まれる Collection View Item の配列が含まれています。

# Segue
Segue は、アプリの UI を定義するすべての Scene 間の関係を提供します。iOS の Storyboard での作業に馴染みがある場合は、通常、iOS　向けの　Segue　ではフルスクリーンビュー間の遷移を定義していることをご存知でしょう。これは macOS と異なります。Segue は通常「 Containment 」を定義し、ある Scene は親 Scene の子です。

macOSでは、ほとんどのアプリは Split View や Tab などの UI 要素を使用して同じウィンドウ内でビューをグループ化する傾向があります。物理的な表示スペースが限られているために画面の表示と非表示を切り替える必要があるiOSとは異なります。

## Presentation Segue
macOSの containment には Modal Window, Sheet View、Popover などの*Presentation Segue* の使用が多いです。macOS は次の組み込み segue タイプを提供します。

* **Show** - Segue のターゲットを非モーダルウィンドウとして表示します。たとえば、このタイプのSegueを使用して、アプリケーションに Document Window の別のインスタンスを表示します。
* **Modal** - Segue のターゲットをモーダルウィンドウとして表示します。たとえば、このタイプのSegueを使用して、アプリケーションのPreferences Window を表示します。
* **Sheet** - 親ウィンドウにアタッチされた Sheet として Segue のターゲットを表示します。たとえば、このタイプの Segue を使用して Find and Replace Sheet を表示します。
* **Popover** - ポップオーバーウィンドウのように Segue のターゲットを表示します。たとえば、この Segue タイプを使用して、ユーザーが UI 要素をクリックしたときのオプションを表示します。
* ** Custom** - 開発者が定義したカスタム Segue タイプを使用して Segue のターゲットを表示します。詳細については、以下のカスタム Segue 作成セクションを参照してください。

Presentation Segue を使用する場合は、親 View Controller の *PrepareForSegue* メソッドを表示の初期化のためにオーバーライドし、 View Controller に任意のデータを提供できます。（訳注: ここ訳怪しい… variable が何の目的語かわかりません）

## Triggered Segue
Triggered Segue では、（ Interface Builder の **Identifier** プロパティーを介して）名前付き Segue を指定し、ユーザーがボタンをクリックするか、コード内の *PerformSegue* メソッドを呼び出すなどのイベントによってトリガーさせることができます。

```csharp
// Display the Scene defined by the given Segue ID
PerformSegue("MyNamedSegue", this);
```

Segue ID は、アプリケーションの UI をレイアウトするときに、 Xcode の Interface Builder 内で定義されます。

<img width="606" alt="スクリーンショット 2017-08-18 19.43.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/33d6c40f-86bb-969f-1325-87aff1404162.png">

Segue のソースとして動作している View Controller では、Segueが実行されて指定された　View Controller　が表示される前に、**PrepareForSegue** メソッドをオーバーライドして初期化を行う必要があります。


```csharp
public override void PrepareForSegue (NSStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue (segue, sender);

    // Take action based on Segue ID
    switch (segue.Identifier) {
    case "MyNamedSegue":
        // Prepare for the segue to happen
        ...
        break;
    }
}
```

必要に応じて、`ShouldPerfromSegue` メソッドをオーバーライドして、Segue が C＃ コードで実際に実行されるかどうかを制御できます。手動で表示された View Controller の場合は、`DismissController` メソッドを呼び出して必要なくなったときに表示から削除します。

## カスタム Segue の作成
アプリケーションを作成するにあたり、macOS の組み込み Segue では提供されていない Segue タイプが必要なときもあります。そういうときは、アプリの UI をレイアウトするときに Xcode の Interface Builder で　割り当てることができるカスタム Segue を作成することができます。

たとえば、（新しいウィンドウでターゲット Scene を開く代わりに）ウィンドウ内の現在の View Controller を置き換える新しい Segue タイプを作成するには、次のコードを使用します。

```csharp
using System;
using AppKit;
using Foundation;

namespace OnCardMac
{
    [Register("ReplaceViewSeque")]
    public class ReplaceViewSeque : NSStoryboardSegue
    {
        #region Constructors
        public ReplaceViewSeque() {

        }

        public ReplaceViewSeque (string identifier, NSObject sourceController, NSObject destinationController) : base(identifier,sourceController,destinationController) {

        }

        public ReplaceViewSeque (IntPtr handle) : base(handle) {
        }

        public ReplaceViewSeque (NSObjectFlag x) : base(x) {
        }
        #endregion

        #region Override Methods
        public override void Perform ()
        {
            // Cast the source and destination controllers
            var source = SourceController as NSViewController;
            var destination = DestinationController as NSViewController;

            // Swap the controllers
            source.View.Window.ContentViewController = destination;

            // Release memory
            source.RemoveFromParentViewController ();
        }
        #endregion

    }

}
```

いくつか注意すべきことがあります。

* このクラスを Objective-C / macOS に公開するために、 `Register` 属性を使用しています。
* カスタム Segue のアクションを実行するために `Perform` メソッドをオーバーライドしています。
* Window の `ContentViewController` コントローラを、 Segue のターゲット（行き先）によって定義されたものに置き換えます。
* 元の View Controller を削除し、 `RemoveFromParentViewController` メソッドを使用してメモリを解放します。

この新しい Segue タイプを Xcode の Interface Builder で使用するには、まずアプリケーションをコンパイルしてから Xcode に切り替え、2つの Scene の間に新しい Segue を追加する必要があります。 **Style** を **Custom** に設定し、 **Segue Class** を `ReplaceViewSegue` （カスタム Segue クラスの名前）に設定します。

<img width="609" alt="スクリーンショット 2017-08-18 20.08.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/6a2845ab-239c-a217-d345-d90cc368eb76.png">

# Window Controller
Window Controller は macOS アプリケーションが作成できるさまざまな Window タイプを含み、制御します。Storyboard　の場合、以下の機能があります。

1. Content View Controller を提供する必要があります。これは、子 Window と同じ Content View Controller になります。
2. Storyboard プロパティには、 Window Controller のロード元の Storyboard が含まれます。Storyboard からロードされていない場合は、 null が返されます。
3. `DismissController` メソッドを呼び出して、指定された Window を閉じてビューから削除することができます。

View Controller と同様に、Window Controller は `PerformSegue`、 `PrepareForSegue`、 `ShouldPerfromSegue` メソッドを実装し、Segue 操作のソースとして使用できます。

Window Controller　は　macOS　アプリケーションの以下の機能を担います。

* 特定の Window を管理します。
* Window の Title Bar と Toolbar  （使用可能な場合）を管理します。
* Content View Controller を管理して、Window の内容を表示します。

# Gesture Recognizer
macOS の　Gesture Recognizer　は、iOS の機能とほぼ同じです。開発者は、アプリの UI の要素にジェスチャー（マウスボタンをクリックするなど）を簡単に追加することができます。

しかし、iOS のジェスチャーがアプリのデザイン（2本の指で画面をタップするなど）によって決まるとすると、macOS　のほとんどのジェスチャーはハードウェアによって決まります。Gesture Recognizer　を使用すると、UI　のアイテムにカスタムインタラクションを追加するのに必要なコード量を大幅に削減できます。ダブルクリックとシングルクリックを自動的に判断できます。

View Controller で MouseDown イベントをオーバーライドする代わりに、Storyboard を操作する際に Gesture Recognizer を使用してユーザー入力イベントを処理すべきです。

macOS　では、以下の Gesture Recognizer が利用できます。

* `NSClickGestureRecognizer` - マウスダウン・アップのイベントを登録します。
* `NSPanGestureRecognizer ` - マススボタンダウン、ドラッグ、離すイベントを登録します。
* `NSPressGestureRecognizer` - 特定時間マウスボタンを押し続けるイベントを登録します。
* `NSMagnificationGestureRecognizer` - トラックパッドから倍率イベントを登録します。
* `NSRotationGestureRecognizer` - トラックパッドからの回転イベントを登録します。

# Storyboard Reference の使用
Storyboard Reference　を使用すると、大きく複雑な　Storyboard デザインを取り込んで、元の　Storyboard　から参照される小さな Storyboard に分割し、複雑さを排除します。その結果、個々の　Storyboard　の設計と保守を容易にすることができます。

さらに、Storyboard Reference　は、同じ　Storyboard　内の別の Scene または別の Storyboard の特定の Scene に *アンカー* を提供することができます。

## 外部　Storyboard の参照
外部 Storyboard への参照を追加するには、次の手順を実行します。

1. **ソリューションエクスプローラ** でプロジェクト名を右クリックし、 **Add** > **New Fole...** > **Mac** > **Storyboard** うぃ選択します。新しい Storyboard の **Name**
 を入力し、**New** ボタンをクリックします。

<img width="469" alt="スクリーンショット 2017-08-18 20.58.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/120bb311-90ca-d6ad-60ec-50e00adb516c.png">

2. **ソリューションエクスプローラ**で、新しい Storyboard 名をダブルクリックし、Xcode のInterface Builderで編集するために開きます。

3. いつものように新しい Storyboard の scene のレイアウトをデザインし、変更を保存します。

<img width="473" alt="スクリーンショット 2017-08-18 21.00.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/168f6000-cd2d-ce54-27a2-f1f4e281a02f.png">

4. Interface Builderで参照を追加する Storyboard に切り替えます。
5. **Storyboard Reference** を **Object Library** からデザインサーフェスにドラッグします。

<img width="464" alt="スクリーンショット 2017-08-18 21.04.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/67a67d7a-8312-af0a-50e9-40189ec0f7bd.png">

6. **Attribute Inspector** で、上記で作成した Storyboard の名前を選択します。

<img width="273" alt="スクリーンショット 2017-08-18 21.05.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/27aff62a-8370-1470-63bc-cb949761c246.png">

7. 既存の Scene のUI ウィジェット（ボタンのような）を Control + クリックし、作成した Storyboard Reference の新しい Segue を作成します。Segue を完了するためにポップアップメニューから **Show** を選択します。

<img width="163" alt="スクリーンショット 2017-08-18 21.11.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/6c3b91b4-9197-3fa2-83c4-d66ac7b1e544.png">

8. Storyboard への変更を保存します。
9. Xamarin Studioに戻り、変更を同期します。

アプリが実行され、ユーザーが Segue を作成した UI 要素をクリックすると、Storyboard Referenceで指定された外部 Storyboard の Initial Window Controller が表示されます。

## 外部 Storyboard 内特定の Scene の参照
外部 Storyboard （で Initial Window Controller でないもの）の特定の Scene への参照を追加するには、以下を実行します。

1. **ソリューションエクスプローラ** で、外部 Storyboard をダブルクリックして、Xcode の Interface Builder で編集するために開きます。
2. 新しい Scene を追加し、普段どおりにそのレイアウトをデザインします。

<img width="461" alt="スクリーンショット 2017-08-18 21.18.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/50c21e89-d6fd-0c5f-1df8-ef090edb8a64.png">

3. **Identity Inspector** で新しい Scene の Window Controller の **Storyboard ID** を入力します。

<img width="270" alt="スクリーンショット 2017-08-18 21.19.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/3fda86c7-53f7-a177-c287-f732e22e5fb1.png">

4. Interface Builder で参照を追加する Storyboard を開きます。
5. **Storyboard Reference** を **Object Library** からデザインサーフェスにドラッグします。

<img width="465" alt="スクリーンショット 2017-08-18 21.22.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/bcdec2a2-27b5-8b63-5584-7ac86e567632.png">

6. **Identity Inspector** で、**Storyboard** の名前と上記で作成したシーンの**Reference ID**（ Storyboard ID ）を選択します。

<img width="272" alt="スクリーンショット 2017-08-18 21.24.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/3bd79db8-f107-a2ce-6c85-d621ff932b1f.png">

7. 既存の Scene のUI ウィジェット（ボタンのような）を Control + クリックし、作成した Storyboard Reference の新しい Segue を作成します。Segue を完了するためにポップアップメニューから **Show** を選択します。

<img width="164" alt="スクリーンショット 2017-08-18 21.26.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/dd2067ad-5a4f-7b34-d744-4a6a602f58ae.png">

8. Storyboard への変更を保存します。
9. Xamarin Studioに戻り、変更を同期します。

アプリが実行され、ユーザーが Segue を作成した UI 要素をクリックすると、Storyboard Referenceで指定された外部 Storyboard から特定の **Storyboard ID** を持つ Scene が表示されます。

## 同じ Storyboard の特定 Scene の参照
同じ Storyboard の特定の Scene への参照を追加するには、以下を実行します。

1. **ソリューションエクスプローラ** で、 Storyboard をダブルクリックして編集するために開きます。
2. 新しい Scene を追加し、普段どおりにそのレイアウトをデザインします。

<img width="460" alt="スクリーンショット 2017-08-18 21.31.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/466f9c48-1d80-31f9-2883-08eebf355bd5.png">

3. **Identity Inspector** で、新しい Scene の Window Controller の **Storyboard ID** を入力します。

<img width="273" alt="スクリーンショット 2017-08-18 21.32.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/2c2f1b93-dc48-f9b3-a132-73531c33e0f5.png">

4. **Storyboard Reference** を ** Toolbox** からデザインサーフェスにドラッグします。

<img width="461" alt="スクリーンショット 2017-08-18 21.33.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/3eb36cd0-dd3d-8d88-1734-297ee5feea2b.png">

5. **Attribute Inspector** で、上記で作成した Scene の **Reference ID** （Storyboard ID）を選択します。

<img width="273" alt="スクリーンショット 2017-08-18 21.36.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/55b4ca4e-31c9-f813-3e6e-969fd7325428.png">

6. 既存の Scene のUI ウィジェット（ボタンのような）を Control + クリックし、作成した Storyboard Reference の新しい Segue を作成します。Segue を完了するためにポップアップメニューから **Show** を選択します。

<img width="175" alt="スクリーンショット 2017-08-18 21.37.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/46547/4b557bdd-4bac-b28b-8554-c8b5a7ed5856.png">

7. Storyboard への変更を保存します。
8. Xamarin Studioに戻り、変更を同期します。

アプリケーションが実行され、ユーザーが Segue を作成した UI 要素をクリックすると、Storyboard Reference で指定された同じ Storyboard 内の指定された **Storyboard ID** を持つ Scene
 が表示されます。

# 複雑な Storyboard の例
Xamarin.Mac アプリケーションで Storyboards を操作する複雑な例については、[SourceWriter Sample App](https://developer.xamarin.com/samples/mac/SourceWriter/) を参照してください。SourceWriter は、コード補完と簡単な構文ハイライトをサポートするシンプルなソースコードエディタです。

SourceWriter コードはコメント付きで、利用可能な場合は重要なテクノロジーや方法から Xamarin.Mac
 ガイドの関連情報へのリンクが提供されています。