iOS8から変更になったUISplitViewControllerについて調べてみた


iOS8からUISplitViewControllerが大きく変更になり、
iPadだけでなく、iPhoneでも使えるようになりました。

その新しいUISplitViewControllerについて分かった範囲でまとめてみました。

## リファレンスで出てくる単語について

### 状態を表す単語

#### expanded
* UISplitViewControllerが２つのViewControllerを持ち、分割表示が可能な状態
* horizontal size class が"compact"の時、この状態となる


#### collapsed
* UISplitViewControllerが1つのViewControllerしか持っておらず、分割表示ができない状態
* horizotal size class が"regular"の時、この状態となる

### Split画面を表す単語

#### primary

* 分割表示時の左側
* UISplitViewControllerのviewCotnrollersの0番目

#### secondary

* 分割表示時の右側
* UISplitViewControllerのviewCotnrollersの1番目

## 表示判定は、iPhone/iPadではなくsize classを見て行われる

横幅のsize classがRegularの場合に分割表示となり、
横幅のsize classがCompactの場合に1画面表示となる

## Expaded状態のときの表示モード指定

* Automatic (デフォルト)

	環境に応じて自動的に切り替わる。
	例えば、iPadなら縦向きの場合、PrimaryOverlay。横向きの場合、PrimaryHiddenとなる。
* PrimaryHidden

	primary画面を隠し、secondary画面だけを表示する。

* AllVisible

	primary画面とsecondary画面の両方を隠すことなく表示する

* PrimaryOverlay

	seconary画面に覆いかぶさる形でprimary画面が表示される






## 参考にするサンプル

[AppleのサンプルコードAdaptivePhotos](https://developer.apple.com/library/ios/samplecode/AdaptivePhotos/Introduction/Intro.html#//apple_ref/doc/uid/TP40014636)

## ポイント

サンプルの中で、UISplitViewControllerを扱う上で重要そうなところについて調べてみました。

### 1. UISplitViewControllerの生成

ここでは、Expanded時の表示モードの指定も行っています

``` Objective-c:AAPLAppDelegate.m
UISplitViewController *controller = [[UISplitViewController alloc] init];

controller.delegate = self;

AAPLListTableViewController *master = [[AAPLListTableViewController alloc] init];

master.user = user;
UINavigationController *masterNav = [[UINavigationController alloc] initWithRootViewController:master];

AAPLEmptyViewController *detail = [[AAPLEmptyViewController alloc] init];

controller.viewControllers = @[masterNav, detail];
// Expanded時の表示モードを指定
controller.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
self.window.rootViewController = controller;
```

### 2. UISplitViewControllerDelegateの実装

Collapsed状態になるときに、primary画面とsecondary画面のどちらを表示するか指定する
primary画面を表示する場合、YES
secondary画面を表示する場合、NO
NOの場合、primary側がUINavigationControllerであれば、
viewControllersの1番目のViewControllerが表示される。
そのため、ここでは、表示したいViewControllerを
UINavigationControllerのviewControllersにセットしている。
ちなみに、primary側がただのViewController場合、以下のメソッドが実行される。
> collapseSecondaryViewController:forSplitViewController:splitViewController

``` Objective-c:AAPLAppDelegate.m
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController
{
    AAPLPhoto *photo = [secondaryViewController aapl_containedPhoto];
    if (!photo) {
        // If our secondary controller doesn't show a photo, do the collapse ourself by doing nothing.
        return YES;
    }
    
    // Before collapsing, remove any view controllers on our stack that don't match the photo we are about to merge on.
    if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
        NSMutableArray *viewControllers = [NSMutableArray array];
        for (UIViewController *controller in [(UINavigationController *)primaryViewController viewControllers]) {
            if ([controller aapl_containsPhoto:photo]) {
                [viewControllers addObject:controller];
            }
        }
        [(UINavigationController *)primaryViewController setViewControllers:viewControllers];
    }
    return NO;
}
```

Expanded状態になるときに、secondary側に表示するViewControllerを指定する。
nilを返す場合、primary側がUINavigationControllerであれば、
そのviewControllersの0番目がprimary側、1番目がsecondary側に表示される。
ちなみに、primary側がただのViewControllerの場合、以下のメソッドが実行される。
> separateSecondaryViewControllerForSplitViewController:

``` Objective-c:AAPLAppDelegate.m
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController
{
    if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
        NSArray *viewControllers = [(UINavigationController *)primaryViewController viewControllers];
        for (UIViewController *controller in viewControllers) {
            if ([controller aapl_containedPhoto]) {
                // Do the standard behavior if we have a photo.
                return nil;
            }
        }
    }
    // If there's no content on the navigation stack, make an empty view controller for the detail side.
    return [[AAPLEmptyViewController alloc] init];
}
```

### 3. primary側のViewController実装 (AAPLListTableViewController)
ここでは、写真の枚数に応じて、行の表示や選択時の処理を変えている

#### 仕様

* 1枚

	Collapsed時は、セルの右端に「>」を表示し、
	Expanded時は、セル選択時に右に写真詳細を表示

* 2枚以上

	Collapsed時でも、Expanded時でも、セルの右端に「>」を表示し、
	セル選択時にはPushでAAPLConversationViewControllerを表示する
	

ここでは、セルのaccessoryTypeを表示を指定している
iOS8から追加された、targetViewControllerForActionが使うことで、
push遷移が可能かどうか判定し、push可能ならaccessoryTypeを表示している

``` Objective-c:AAPLListTableViewController.m
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Whether to show the disclosure indicator for this cell.
    BOOL pushes;

    if([self shouldShowConversationViewForIndexPath:indexPath]) {
        // If the conversation corresponding to this row has multiple photos.
        pushes = [self aapl_willShowingViewControllerPushWithSender:self];
    } else {
        // If the conversation corresponding to this row has a single photo.
        pushes = [self aapl_willShowingDetailViewControllerPushWithSender:self];
    }

    // Only show a disclosure indicator if selecting this cell will trigger a push in the master view controller (the navigation controller above ourself).
    if(pushes) {
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    } else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }

    AAPLConversation *conversation = [self conversationForIndexPath:indexPath];

    cell.textLabel.text = conversation.name;
}
```

ここでは、セル選択時の表示方法を指定している
写真が複数枚の場合、showViewController実行
これは、UINavigationControllerのpushと同じ動きをする
写真が1枚の場合、showDetailViewController実行
これは、UISplitViewControllerのshowDetailViewControllerの動きをする

* horizotal size classがregularの場合(分割表示)

	secondary側が置き換わる

* horizotal size classがcompactの場合(1画面表示)

	pushで画面遷移する

``` Objective-c:AAPLListTableViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    AAPLConversation *conversation = [self conversationForIndexPath:indexPath];

    if([self shouldShowConversationViewForIndexPath:indexPath]) {
        AAPLConversationViewController *controller = [[AAPLConversationViewController alloc] init];

        controller.conversation = conversation;
        controller.title = conversation.name;

        // If this row has a conversation, we just want to show it.
        [self showViewController:controller sender:self];
    } else {
        AAPLPhoto *photo = [conversation.photos lastObject];
        AAPLPhotoViewController *controller = [[AAPLPhotoViewController alloc] init];

        controller.photo = photo;
        controller.title = conversation.name;

        // If this row has a single photo, then show it as the detail (if possible).
        [self showDetailViewController:controller sender:self];
    }
}
```

以上です。