OS X アプリでStoryboardとSegueを利用する ビューを切り替える編

[OS X アプリでStoryboardとSegueを利用する（概略） ] (http://qiita.com/masakihori/items/ce74f604925f8f66ce1f)
に続いて実践編です

# やること
標準で用意されているセグエをやってもつまらないのでカスタムセグエを利用した今すぐ使えるビューの遷移です。
ウインドウ内の一部をボタンクリックなどでスライドして別のビューに切り替えるセグエを作ります。

「OS Xアプリで」というわけで、iOSでは普通やらなくて、OS XではよくやるであろうWindow内の一部コンテンツの遷移を選びました。

# ストーリーボードを作る
[前回](http://qiita.com/masakihori/items/ce74f604925f8f66ce1f)同様、WindowControllerを配置します。
ウインドウコンテントビューにすでにViewControllerというNSViewControllerが設定されていますのでそのまま利用します。


ウインドウコンテントビューにContainerViewを配置します。
一部が切り替わっていることが分かりやすいように右半分だけに配置しました。
![スクリーンショット 2016-04-03 20.09.44.png](https://qiita-image-store.s3.amazonaws.com/0/2117/a82fbca1-b4b2-0964-5cc6-c4ca0a43d814.png)


新しいNSViewControllerのクラスはそのままでOKです。
ボタンとラベルを配置しました。
![スクリーンショット 2016-04-03 20.10.54.png](https://qiita-image-store.s3.amazonaws.com/0/2117/d339cf54-fe1a-19a2-5f1b-d0e31bda293c.png)


さらにもう一つViewControllerを配置します。カスタムクラスは設定不要です。
こちらもボタンとラベルを配置しました。
![スクリーンショット 2016-04-03 20.11.02.png](https://qiita-image-store.s3.amazonaws.com/0/2117/7ff923aa-0551-5459-021a-6895be0e36ee.png)


このふたつのビューをセグエを利用して切り替えます。
ボタンの「Triggerd Segues」の「action」をもう一方のビューコントローラに接続します。接続先は「custom」を選択します。
![全画面_2016_04_03_18_29 3.png](https://qiita-image-store.s3.amazonaws.com/0/2117/e314d94a-dc7f-2be5-07af-5a3415c49918.png)


![全画面_2016_04_03_18_29_2.png](https://qiita-image-store.s3.amazonaws.com/0/2117/3608aa0f-a64d-d133-e683-7846ab41296c.png)


設定したセグエのAttribute inspectorでClassを「SlideSegue」とします。このクラスは後で作ります。


![全画面_2016_04_03_18_30 2.png](https://qiita-image-store.s3.amazonaws.com/0/2117/71ac935b-7e1c-0f0b-be36-1138730c60b5.png)

![スクリーンショット 2016-04-03 18.30.46.png](https://qiita-image-store.s3.amazonaws.com/0/2117/6b0ed088-4216-0eac-4d06-c64deb50ebf4.png)



同じ作業を行って双方向に同じセグエを設定します。

# カスタムViewController
特に何も必要ないのですが、遷移アニメーション中にボタンを何度も押せてしまうのでそれを抑止します。

```objc:ViewController.m
#import "ViewController.h"

@implementation ViewController
- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(nullable id)sender
{
	// アニメーション開始後に動作させない
	if([sender respondsToSelector:@selector(setAction:)]) {
		[sender setAction:nil];
	}
	
	NSViewController *d = segue.destinationController;
	d.representedObject = self.representedObject;
}
@end
```

この
```objc
- (void)prepareForSegue:sender:
```
は NSStoryboardSegue.h で宣言されている NSSeguePerforming プロトコルのメソッドです。
NSViewController, NSWindowControllerは標準でこのプロトコルに準拠しています。

```objc
- (void)prepareForSegue:sender: 
```
は名前の通り、セグエが発動する前に変化が起る親のオブジェクトに変化が起るビューコントローラに対してデータのセットなどを行うためのメソッドです。

ボタンを無効化してもいいのですが、見た目が変だったのでactionをなくすという強引な方法で2度目以降のクリックを無視するようにしました。

後ろの2行は今回は特に意味はありません。通常はこのようにrepresentedObjectを通じでデータのセットを行います。


# カスタムSegue

カスタムSegueでビューコントローラの関係性の設定と遷移アニメーションの実行を行います。

```objc:SlideSegue.h
#import <Cocoa/Cocoa.h>

@interface SlideSegue : NSStoryboardSegue

@end
```

```objc:SlideSegue.m
#import "SlideSegue.h"

@implementation SlideSegue
- (void)perform
{
	// NSViewControllerの親子関係を設定
	NSViewController *s = self.sourceController;
	NSViewController *d = self.destinationController;
	NSViewController *p = s.parentViewController;
	if(![p.childViewControllers containsObject:d]) {
		[p addChildViewController:d];
	}
	
	// 遷移アニメーション
	[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
		context.duration = 0.5;
		
		NSRect frame = s.view.frame;
		frame.origin.x = frame.size.width;
		d.view.frame = frame;
		[s.view.superview addSubview:d.view];
		
		NSRect newDFrame = s.view.frame;
		
		NSRect newSFrame = s.view.frame;
		newSFrame.origin.x = -newSFrame.size.width;
		
		s.view.animator.frame = newSFrame;
		d.view.animator.frame = newDFrame;

		d.view.autoresizingMask = s.view.autoresizingMask;
	} completionHandler:^{
		// 今まで表示されていたNSViewControllerの親子関係を解除
		[s removeFromParentViewController];
		[s.view removeFromSuperview];
	}];
}
@end
```

基本的にNSStoryboardSegueのサブクラスではこのメソッドのみをオーバーライドして、セグエの動作を設定します。
セグエでは、アニメーションの定義だけではなく、ビューコントローラの関係性の設定も行います。

# 試してみよう
なんとこれで終わりです。

　ボタンを押すたびに、First ViewとSecond Viewが左にスライドして切り替わります。
カスタムSegueを変えるだけでアニメーションの変更が可能です。


自分で設定するのが面倒な方はGithubにレポジトリを作りましたのでそちらを見てください。
https://github.com/masakih/StoryboardSample01

# まとめ
OS XでのStoryboard、Segueの資料が少なくてかなり困ってます。

変化するNSViewControllerの親子関係を誰がどのように設定するのか悩んでいたのですが、最終的にNSStoryboardSegueのリファレンスで
> A storyboard segue specifies a transition or containment relationship between two scenes in a storyboard, 

という記述を見つけてセグエがそれを担えばいいというのに気付きました。

リファレンスはちゃんと読みましょう。