タブごとにStoryboardを分割する

[＠hedjirog](https://twitter.com/hedjirog) です。Interface Builder や Storyboard は必要に応じて使いたい派です。

今回は **`UITabBarController` で複数のタブを保持する際、タブごとに Storyboard を分割する方法** を紹介します。

※ `UITabBarController` で複数のタブを保持する際に、 **必ず Storyboard を分割することを推奨するものではありません。** 状況に応じて Storyboard の分割を検討してください。

ソースコードは [GitHub](https://github.com/hedjirog/MultiStoryboardsDemo) からダウンロードできます。


## 手順

**4つのタブ (Home, Connect, Discover, Me) を持つアプリケーションの開発を想定し、タブごとに Storyboard を分割することを考えます。**

アプリケーションのルートの View Controller はコードから生成し、各タブの View Controller は `UITabBarController` の初期化時に分割した Storyboard から生成します。

以下、手順の説明です。


### 1. 複数の Storyboard の生成

まずは4つの Storyboard を作成します。

ここでは `Home`, `Connect`, `Discover`, `Me` という4つの Storyboardを作成してみます。

![storyboards.png](https://qiita-image-store.s3.amazonaws.com/0/6087/e7aa8dd7-b302-2e74-e781-8d74350df738.png)


### 2. `UITabBarItem` の配置

それぞれの Storyboard の Initial View Controller で、`UITabBarItem` を配置します。

ここでは Storyboard の Initial View Controller を Navigation Controller とし、そこに `UITabBarItem` を配置してみます。

![tabbaritems.png](https://qiita-image-store.s3.amazonaws.com/0/6087/51b52e15-fddf-5b80-2e68-3aef226afd16.png)


### 3. `UIStoryboard` のカテゴリーによる拡張（必要に応じて）

分割した Storyboard をコードで扱いやすいようにカテゴリーによる `UIStoryboard` クラスの拡張をしましょう。これは必須ではありません。好みや必要に応じて実装してください。

ここでは `MSDExtensions` という `UIStoryboard` のカテゴリーを定義してみます。

```objective-c:UIStoryboard+MSDExtensions.h
#import <UIKit/UIKit.h>

@interface UIStoryboard (MSDExtensions)

+ (instancetype)msd_homeStoryboard;
+ (instancetype)msd_connectStoryboard;
+ (instancetype)msd_discoverStoryboard;
+ (instancetype)msd_meStoryboard;

@end
```

```objective-c:UIStoryboard+MSDExtensions.m
#import "UIStoryboard+MSDExtensions.h"

NSString * const kHomeName = @"Home";
NSString * const kConnectName = @"Connect";
NSString * const kDiscoverName = @"Discover";
NSString * const kMeName = @"Me";

@implementation UIStoryboard (MSDExtensions)

+ (instancetype)msd_homeStoryboard
{
    return [self msd_storyboardWithName:kHomeName];
}

+ (instancetype)msd_connectStoryboard
{
    return [self msd_storyboardWithName:kConnectName];
}

+ (instancetype)msd_discoverStoryboard
{
    return [self msd_storyboardWithName:kDiscoverName];
}

+ (instancetype)msd_meStoryboard
{
    return [self msd_storyboardWithName:kMeName];
}

+ (instancetype)msd_storyboardWithName:(NSString *)name
{
    return [self storyboardWithName:name bundle:nil];
}

@end
```


### 4. アプリケーション特有の `UITabBarController` のサブクラスの定義

`UITabBarController` を継承したクラスを定義して、アプリケーション特有の処理を定義しやすくします。

ここでは `MSDTabBarController` というサブクラスを定義してみます。

```objective-c:MSDTabBarController.h
#import <UIKit/UIKit.h>

@interface MSDTabBarController : UITabBarController

@end
```


### 5. ルートの View Controller の生成

App Delegate の `application:didFinishLaunchingWithOptions:` メソッドでルートの View Controller を設定します。

Window の `rootViewController` プロパティに対して、先ほど定義した `MSDTabBarController` クラスのインスタンスをセットしましょう。

```objective-c:MSDAppDelegate.m
#import "MSDAppDelegate.h"
#import "MSDTabBarController.h"

@implementation MSDAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.rootViewController = [[MSDTabBarController alloc] init];
    [self.window makeKeyAndVisible];

    return YES;
}

@end
```


### 6. 各タブの View Controller の生成

`MSDTabBarController` の初期化処理時に、各タブに保持される View Controller を Storyboard から生成します。

ここでは `viewDidLoad` メソッドが呼ばれるタイミングで、`viewControllers` プロパティの配列を適切に変更するようにしてみます。

```objective-c:MSDTabBarController.m
#import "MSDTabBarController.h"
#import "UIStoryboard+MSDExtensions.h"

@implementation MSDTabBarController

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self setUpViewControllers];
}

- (void)setUpViewControllers
{
    self.viewControllers = @[[[UIStoryboard msd_homeStoryboard] instantiateInitialViewController],
                             [[UIStoryboard msd_connectStoryboard] instantiateInitialViewController],
                             [[UIStoryboard msd_discoverStoryboard] instantiateInitialViewController],
                             [[UIStoryboard msd_meStoryboard] instantiateInitialViewController]
                             ];
}

@end
```


## まとめ

`UITabBarController` で複数のタブを扱う場合に、タブごとに Storyboard を分割する方法を紹介しました。

複数の Storyboard を扱う方法は、すでに [Developers.IO の記事](http://dev.classmethod.jp/smartphone/iphone/use-multiple-storyboards/) でも紹介されています。
該当記事ではメインの画面遷移と共通の画面遷移のために Storyboard を作成する方法であるのに対し、ここでは **メインの画面遷移のために Storyboard は作成せず、（タブごとの）独立した複数の Storyboard を作成してコードから呼び出す方法** であるという点で異なります。

`UITabBarController` を利用しつつ Storyboard を分割する際は、両者を比較し、開発しているアプリケーションにより相応しい方法を選択してください。


## 関連リンク

* [hedjirog/MultiStoryboardsDemo](https://github.com/hedjirog/MultiStoryboardsDemo) (この記事のソースコード)
* [[iOS] 複数のStoryboardを使って画面遷移を作成する ｜ Developers.IO](http://dev.classmethod.jp/smartphone/iphone/use-multiple-storyboards/)