Edited at

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

More than 3 years have passed since last update.

OS X アプリでStoryboardとSegueを利用する(概略)

に続いて実践編です


やること

標準で用意されているセグエをやってもつまらないのでカスタムセグエを利用した今すぐ使えるビューの遷移です。

ウインドウ内の一部をボタンクリックなどでスライドして別のビューに切り替えるセグエを作ります。

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


ストーリーボードを作る

前回同様、WindowControllerを配置します。

ウインドウコンテントビューにすでにViewControllerというNSViewControllerが設定されていますのでそのまま利用します。

ウインドウコンテントビューにContainerViewを配置します。

一部が切り替わっていることが分かりやすいように右半分だけに配置しました。

新しいNSViewControllerのクラスはそのままでOKです。

ボタンとラベルを配置しました。

さらにもう一つViewControllerを配置します。カスタムクラスは設定不要です。

こちらもボタンとラベルを配置しました。

このふたつのビューをセグエを利用して切り替えます。

ボタンの「Triggerd Segues」の「action」をもう一方のビューコントローラに接続します。接続先は「custom」を選択します。

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

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


カスタムViewController

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


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は標準でこのプロトコルに準拠しています。

- (void)prepareForSegue:sender: 

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

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

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


カスタムSegue

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


SlideSegue.h

#import <Cocoa/Cocoa.h>


@interface SlideSegue : NSStoryboardSegue

@end



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,


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

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