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画面が表示される
参考にするサンプル
ポイント
サンプルの中で、UISplitViewControllerを扱う上で重要そうなところについて調べてみました。
1. UISplitViewControllerの生成
ここでは、Expanded時の表示モードの指定も行っています
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
- (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:
- (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を表示している
- (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で画面遷移する
- (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];
}
}
以上です。