Posted at

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

More than 3 years have passed since last update.

iOS8からUISplitViewControllerが大きく変更になり、

iPadだけでなく、iPhoneでも使えるようになりました。

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


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


状態を表す単語


expanded


  • UISplitViewControllerが2つの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


ポイント

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


1. UISplitViewControllerの生成

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


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



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:



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を表示している


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で画面遷移する




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];
}
}


以上です。