iOSアプリ画面遷移3本勝負

前回のあらすじと本日の目標

2012年11月4日の日記では、iPhoneアプリのためのミニマルな雛型を作りました。

今日はそれを拡張して、画面遷移を実装してみたいと思います。ただし簡単のため、実装するのは2画面間を交互に遷移するだけのアプリです。前回ほどロジカルな話題は出てきません。

──それでは、前回のプロジェクトを開いてみましょう。いま、手元には以下のファイルが揃っています。

  • AppDelegate.h
  • AppDelegate.m
  • MainViewControllerFactory.h
  • MainViewController.h
  • MainViewController.m

画面の追加

画面遷移を実装するためには、複数の画面が必要です。

前回のおさらいがてら、サブ画面に相当する「SubViewController」クラスをプロジェクトに追加します(前回作ったMainViewControllerクラスと同じ要領なので解説は省略)。

SubViewController.h

#import <UIKit/UIKit.h>

@interface SubViewController : UIViewController
@end

ViewControllers.h

#import "MainViewController.h"
#import "SubViewController.h"

SubViewController.m

#import "ViewControllers.h"

@interface SubViewController ()
@end

@implementation SubViewController
{
    // ボタンを作ります
    UIButton *button;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // ボタンを初期化します
        button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // ボタンのキャプションを設定
    [button setTitle:@"ボタンです" forState:UIControlStateNormal];
    
    // キャプションに合わせてサイズを自動調整
    [button sizeToFit];
    
    // ボタンを画面中心に移動
    button.center = self.view.center;
    
    // 画面変更時にボタンの位置を自動調整
    button.autoresizingMask = UIViewAutoresizingFlexibleWidth |
    UIViewAutoresizingFlexibleHeight |
    UIViewAutoresizingFlexibleLeftMargin |
    UIViewAutoresizingFlexibleRightMargin |
    UIViewAutoresizingFlexibleTopMargin |
    UIViewAutoresizingFlexibleBottomMargin;
    
    // ボタン押下時のイベントを登録
    [button addTarget:self action:@selector(buttonDidPush) forControlEvents: UIControlEventTouchUpInside];
    
    // ボタンを画面に追加
    [self.view addSubview:button];
}

// ボタン押下時のイベント
- (void)buttonDidPush
{
    NSLog(@"ボタンが押されました");
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}
@end

以上で、新しい画面ができました。まだボタンを押しても何も起きません。

3パターンの画面遷移

いよいよ、画面遷移の実装です。一口に画面遷移と言っても、3通りほど基本パターンがあります*1

モーダル画面の表示

アプリのメイン画面を起点にして、コンフィグ画面やオプション画面など、関連のある不特定多数の画面へ遷移する際に使われるパターンです。また、下図のように「表画面」と「裏画面」の関係を持つ画面間の遷移もこの手法で実装します。
f:id:tercel_s:20121112230003p:plain:w300

階層ナビゲーション

複数の画面の親子関係がはっきりしている場合に使われるパターンで、『モバイルデザインパターン― ユーザーインタフェースのためのパターン集』ではDrill Downパターンとして紹介されています(p.125)。標準アプリの「リマインダー」などが典型的なお手本でしょう。
f:id:tercel_s:20121112231129p:plain:w150

タブ

モバイルデザインパターン― ユーザーインタフェースのためのパターン集』では、そのものズバリTabsパターンとして冒頭を飾っており(p.10)、iPhoneの使い手にとっても非常に馴染み深い手法であります。画面下部にあるタブバーのアイコンを親指でタップする事で、軽快に画面を切り替える事ができます。
f:id:tercel_s:20121112232250p:plain:w150

3つとも、用途も違えば実装方法も違いますから、状況に応じてそれぞれを使い分けられるよう、実装例を示したいと思います。

モーダル画面

まずは一番実装が簡単な「モーダル画面」から。先ほどのプロジェクトのうち、修正するのは遷移元と遷移先の2つのViewControllerクラスのみです。

MainViewController.m(抜粋)

- (void)buttonDidPush
{
    NSLog(@"ボタンが押されました");
    
    // 次画面を指定して遷移
    UIViewController *next = [[SubViewController alloc] init];
    next.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
    [self presentViewController:next animated:YES completion:^ {
        // 完了時の処理をここに書きます
    }];
}

SubViewController.m(抜粋)

// ボタン押下時のイベント
- (void)buttonDidPush
{
    NSLog(@"ボタンが押されました");
    
    // 前画面に戻る
    [self dismissViewControllerAnimated:YES completion:^{
        // 完了時の処理をここに書きます
    }];
}

なお、UIViewControllerの表示の際のアニメーションは、modalTransitionStyleプロパティによって以下の値を設定することができます*2

  • UIModalTransitionStyleCoverVertical (次の画面が下からせり出し、閉じるときは下に隠れる)
  • UIModalTransitionStyleCrossDissolve (前の画面の裏側に次の画面があり、水平方向に回転する)
  • UIModalTransitionStyleFlipHorizontal (前の画面がうっすらと消えていき、同時に次の画面がうっすらと出てくる)

以上が、モーダル画面による画面遷移の実装です。

階層ナビゲーション

次は「階層ナビゲーション」です。ここでは、MainViewControllerFactory.m を書き換える必要があります。

MainViewControllerFactory.m

#import "MainViewControllerFactory.h"
#import "ViewControllers.h"

@implementation MainViewControllerFactory

+ (UIViewController*) createMainViewController
{
    // メインとなる ViewController のインスタンスを生成して返す
    return [[UINavigationController alloc]
            initWithRootViewController:[[MainViewController alloc] init]];
}

@end

今までは、MainViewController のインスタンスをそのまま返していましたが、ここでは UINavigationController で包んでいますね。

MainViewController.m(抜粋)

// ボタン押下時のイベント
- (void)buttonDidPush
{
    NSLog(@"ボタンが押されました");

    // 次画面を指定して遷移
    UIViewController* next = [[SubViewController alloc] init];
    [self.navigationController pushViewController:next animated:YES];
}

SubViewController.m(抜粋)

- (void)buttonDidPush
{
    NSLog(@"ボタンが押されました");
    
    // 前画面に戻る    
    [self.navigationController popViewControllerAnimated:YES];
}

以上が、階層ナビゲーションによる画面遷移の実装です。

タブ

最後は「タブ」です。これだけは少々特殊で、タブに表示したいViewControllerを予めすべてインスタンス化して、UITabBarControllerに紐づけておく必要があります。

MainViewControllerFactory.m

#import "MainViewControllerFactory.h"
#import "ViewControllers.h"

@implementation MainViewControllerFactory

+ (UIViewController*) createMainViewController
{
    NSMutableArray *viewControllers = [[NSMutableArray alloc] init];
    
    // タブに登録したいViewControllerを配列に追加
    [viewControllers addObject:[[MainViewController alloc] init]];
    [viewControllers addObject:[[SubViewController alloc] init]];
    
    // タブを作成して返す
    UITabBarController *tabBarController = [[UITabBarController alloc] init];
    tabBarController.viewControllers = viewControllers;
    return tabBarController;    
}


また、タブに表示するアイコンなどは、各ViewControllerのイニシャライザに記述します。実装例は以下のようになります。なお、画面遷移ボタンは要らない子なので今回は省きました。

MainViewController.m(抜粋)

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // タブに表示する情報を設定します
        self.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:
                           UITabBarSystemItemFeatured tag:0];
    }
    return self;
}

SubViewController.m(抜粋)

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // タブに表示する情報を設定します
        self.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:
                           UITabBarSystemItemFavorites tag:1];
    }
    return self;
}

各ViewControllerクラスのイニシャライザでは、タブの画像を設定しています。initWithTabBarSystemItem: tag: メソッドには、以下の定数を指定できます*3

  • UITabBarSystemItemMore (その他)
  • UITabBarSystemItemFavorites (よく使う項目)
  • UITabBarSystemItemFeatured (おすすめ)
  • UITabBarSystemItemTopRated (トップレート)
  • UITabBarSystemItemRecents (履歴)
  • UITabBarSystemItemContacts (連絡先)
  • UITabBarSystemItemHistory (履歴)
  • UITabBarSystemItemBookmarks (ブックマーク)
  • UITabBarSystemItemSearch (検索)
  • UITabBarSystemItemDownloads (ダウンロード)
  • UITabBarSystemItemMostRecent (最新)
  • UITabBarSystemItemMostViewed (人気)

以上が、タブによる画面遷移の実装です。

まとめ

ここまでで、3通りの画面遷移のパターンを見てきました。しかし、まだ今のままでは不十分なところがあります。

アプリの中では一つひとつのViewControllerが独立してしまっているため、複数の画面をまたいだデータの受け渡しを実現するための仕組みを作る必要があるのです。

そこで次回は、データの受け渡しを実現するためのクラス設計について考えたいと思います。

*1:iPadを含めると、もう少しバリエーションが増えます。

*2:本当はUIModalTransitionStylePartialCurlもあるのですが、使い方が面倒だったので除外しました。

*3:オリジナルの画像を使用する事もできますが、面倒なのでここでは既製品を流用しました。

Copyright (c) 2012 @tercel_s, @iTercel, @pi_cro_s.