前回のつづき

サンプルをダウンロードして起動しましょう。

https://github.com/stack3/STUniversalSample

iPhoneで起動した場合

f:id:eimei23:20130402132906p:plain

タブによって画面が切り替わります。

iPadで起動した場合

f:id:eimei23:20130402132919p:plain

Master(左画面) & Detail(右画面)の2ペインです。MasterのメニューによってDetailが切り替わります。

設定はPopoverで開くようになっています。

f:id:eimei23:20130402141551p:plain

サンプルアプリの構成

本サンプルは、これら3つの画面によって構成されるダミーアプリです。

  • タイムライン画面(STTimelineViewController)。ユーザーの投稿表示のダミー画面。
  • ユーザー画面(STUserViewController)。ユーザーの情報表示のダミー画面。
  • 設定画面(STSettingsViewController)。設定表示のダミー画面。

iPhone、iPadでの画面の切り分け

STAppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        self.viewController = [UITabBarController tabBarControllerForRootView];
    } else {
        self.viewController = [[STSplitViewController alloc] init];
    }
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

このようにしてiPhone、iPadで初期画面を切り分けています。

  • UI_USER_INTERFACE_IDIOM()にてiPhoneかiPadの判定
  • iPhoneならばUITabBarControllerを生成してrootViewControllerにする
  • iPadならばUISplitViewControllerを生成してrootViewControllerにする

UITabBarController+STPhoneRootView.m

+ (id)tabBarControllerForRootView
{
    UITabBarController *tabBarController = [[UITabBarController alloc] init];
    
    STTimelineViewController *timelineViewController = [[STTimelineViewController alloc] init];
    STUserViewController *userViewController = [[STUserViewController alloc] init];
    STSettingsViewController *settingsViewController = [[STSettingsViewController alloc] init];

    tabBarController.viewControllers = @[timelineViewController, userViewController, settingsViewController];
    
    return tabBarController;
}

UITabBarControllerにtabBarControllerForRootViewというメソッドを用意して、

  • UITabBarControllerオブジェクトの生成
  • 各画面のViewControllerの生成 
  • それらをUITabBarControllerの各タブに対応した画面として設定

を行なっています。

UITabBarControllerのSubclassを作ることは推奨しないとドキュメントに書いてあるので、カテゴリメソッドで初期化するようにしています。

対してiPadではUISplitViewControllerのSubclass、STSplitViewControllerを作って、そのinitメソッドで初期化するようにしています。

STSplitViewController.m

@implementation STSplitViewController {
    __strong STPadMasterViewController *_masterViewController;
    __strong STTimelineViewController *_timelineViewController;
    __strong STUserViewController *_userViewController;
    __strong STSettingsViewController *_settingsViewController;
    __strong UIPopoverController *_popoverController;
}

- (id)init
{
    self = [super init];
    if (self) {
        _masterViewController = [[STPadMasterViewController alloc] init];
        _masterViewController.delegate = self;
        
        _timelineViewController = [[STTimelineViewController alloc] init];
        _userViewController = [[STUserViewController alloc] init];
        _settingsViewController = [[STSettingsViewController alloc] init];
        
        self.viewControllers = @[_masterViewController, _timelineViewController];
    }
    return self;
}
  • MasterとなるSTPadMasterViewControllerを生成
  • 各画面のViewControllerを生成
  • UISplitViewController#viewControllersにSTPadMasterViewControllerとSTTimelineViewController(初期画面)を設定する

Detail画面が切り替わった時に以前の状態を保持するために、各画面のViewControllerのオブジェクトは非表示中も保持するようにしておきます。

STPadMasterViewController.xib

f:id:eimei23:20130402135601p:plain

STPadMasterViewController#viewのSubviewは以下のとおり。

  • メニューとしてTimeline/Userを表示するためのUITableView
  • 設定を表示するためのUIButton

STPadMasterViewController#viewのAttributes Inspectorはこのようになっています。

f:id:eimei23:20130402135935p:plain

Simulated Metricsの各項目は、あくまでInterfaceBuilder上でのシミュレートのためのもので実際の動作には影響しないようです。

MasterなのでSizeプロパティをMasterにしておいてもよいのですが、NoneにしておくとInterface Builder上で好きなサイズに変更できます。自分は編集上見やすいサイズにしておく方が好きなので、どの画面のxibもSizeはNoneにしています。

少し話がそれました。

STPadMasterViewController.m

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row == _STMenuItemTimeline) {
        [_delegate padMasterViewControllerDidSelectTimeline:self];
    } else if (indexPath.row == _STMenuItemUser) {
        [_delegate padMasterViewControllerDidSelectUser:self];
    }
}

#pragma mark - Button Events

- (void)didTapSettingsButton
{
    [_delegate padMasterViewController:self didSelectSettingsInView:_settingsButton];
}

TableView上のTimeline/Userを押された時のイベントと設定ボタンを押された時のイベントで、Delegateメソッドを呼び出すようにしています。

STPadMasterViewDelegateはこのように定義されています。

STPadMasterViewController.h

@protocol STPadMasterViewControllerDelegate <NSObject>

- (void)padMasterViewControllerDidSelectTimeline:(STPadMasterViewController *)sender;
- (void)padMasterViewControllerDidSelectUser:(STPadMasterViewController *)sender;
- (void)padMasterViewController:(STPadMasterViewController *)sender didSelectSettingsInView:(UIView *)inView;

@end

このDelegateメソッドはSTSplitViewControllerよって実装されます。STSplitViewControllerはメニューが押された時のイベントを検知して、適切なViewControllerをDetail(右側の画面)に設定します。設定ボタンは、UIPopoverControllerを使ってPopoverとしてSTSettingsViewControllerを表示しています。

STSplitViewController.m

- (void)padMasterViewControllerDidSelectTimeline:(STPadMasterViewController *)sender
{
    self.viewControllers = @[_masterViewController, _timelineViewController];
}

- (void)padMasterViewControllerDidSelectUser:(STPadMasterViewController *)sender
{
    self.viewControllers = @[_masterViewController, _userViewController];
}

- (void)padMasterViewController:(STPadMasterViewController *)sender didSelectSettingsInView:(UIView *)inView
{
    _popoverController = [[UIPopoverController alloc] initWithContentViewController:_settingsViewController];
    [_popoverController presentPopoverFromRect:inView.bounds
                                        inView:inView
                      permittedArrowDirections:UIPopoverArrowDirectionLeft
                                      animated:YES];
} 

iPhoneとiPadでサイズが異なる各画面のxibについて

iPhone、iPad共通で、かつサイズが異る各画面のxibは以下のとおりです。

  • タイムライン画面のSTTimelineViewController.xib
  • ユーザー画面のSTUserViewController.xib
  • 設定画面のSTSettingsViewController.xib

注目すべきなのはiPhoneとiPadでファイルを分けていないことです。

チュートリアルその1で述べたように多くの画面は、iPhone/iPadでSubviewのサイズが異なるだけだと思います。その場合、ViewControllerだけでなくxibも共通にしておくのが良いと思います。そしてAuto LayoutのConstraint、もしくはAutoresizing Maskをきちんと設定し、Viewのサイズが異なっても適切に配置されるようにしておきます。

xibファイルを作るとき

UIViewControllerを作るときに以下のようなダイアログが出ます。この時「Targeted for iPad」のチェックはオフにしています。

f:id:eimei23:20130402142847p:plain

またxibファイル単体で作ろうとした場合は、以下のようなダイアログが出ます。この時「iPhone」を選択します(デフォルト)。

f:id:eimei23:20130402142859p:plain

iPhoneで作ってもiPadでも使うことができます。逆にiPadで作ってもiPhoneでも使えますが、iPadにするとInterface Builder上での編集サイズが大きくなり、編集しずらいです。使いまわすならiPhoneにして作っておくのが良いと思います。

※ どちらで作ってもAttributes InspectorでSizeをNoneにすればサイズ調整できますが。

ほとんどは一緒だが一部が違う場合は?

この場合もViewControllerとxibは共通にしておいて、ViewControllerで異なる部分だけUI_USER_INTERFACE_IDIOM()でiPhone/iPadを判断し、それぞれに適した処理をすると良いと思います。

iPhoneとiPadで画面構成が全く違う場合は?

さすがにこの時は、ViewControllerとxib両方別にするのが良いでしょう。

いずれにせよ、すべてのViewControllerでXXXViewController_iPhone.xib、XXXViewController_iPad.xibのように分けるのは管理コストの面から避けたほうが良いと自分は思います。

その3へつづく