UINavigationController試しました

一つのiPhoneアプリにいろいろな画面を実装する必要がある場合、よく使われるUINavigationを試しました。
なぜかUIINavigationControllerのサンプルがTableViewControllerとセットになっていたのでわかりやすいようにサンプルを作ってみました。

結果、なぜUIINavigationControllerのサンプルがTableViewControllerなのか、なんとなくわかりました。
なので、UIINavigationControllerのサンプルがTableViewControllerばかりなのは何故かを知っている人はこの記事を読まなくて良いです。

コードについて

Logを出力するためにいたるところにLOG_METHODを仕込んでます。
以下を参考にしています。

http://d.hatena.ne.jp/Psychs/20081120/1227203259

それから私はInterface Builderを使いませんのでxibファイルは削除しています。
下記サイトを参考にしました。

http://webos-goodies.jp/archives/how_to_create_an_iphone_app_without_interface_builder.html

処理を順番に見てみる

最初にmainがよばれて、AppDelegateを呼びます。

#import <UIKit/UIKit.h>

int main(int argc, char *argv[]) {
    
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, @"AppDelegate");
    [pool release];
    return retVal;
}

次に、AppDelegate.hとAppDelegate.mを見てみます。

// AppDelegate.h
#import <UIKit/UIKit.h>

@interface AppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *_window;
	UINavigationController *_nav;
}

@end
#import "AppDelegate.h"
#import "RootViewController.h"

@implementation AppDelegate

#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    LOG_METHOD;

	RootViewController * rootController = [[RootViewController alloc] init];
	_nav = [[UINavigationController alloc] initWithRootViewController:rootController];	

	// IB使わない場合は必要な処理
	CGRect frameForWindow = [[UIScreen mainScreen] bounds];
	_window = [[UIWindow alloc] initWithFrame:frameForWindow];
	[_window makeKeyAndVisible];	
	[_window addSubview:_nav.view];

    return YES;
}

- (void)dealloc {
	LOG_METHOD;

    [_window release];
	[_nav release];
    [super dealloc];
}

@end

以下の処理は、nibファイルを削除してあるので、nibファイルがやっていた処理を自前で記述しています。

	// IB使わない場合は必要な処理
	CGRect frameForWindow = [[UIScreen mainScreen] bounds];
	_window = [[UIWindow alloc] initWithFrame:frameForWindow];
	[_window makeKeyAndVisible];	
	[_window addSubview:_nav.view];

次の処理で起点となるViewControllerのインスタンスを生成しています。
UINavigationControllerのインスタンスは必ず起点となるViewControllerのインスタンスが必要です。

	RootViewController * rootController = [[RootViewController alloc] init];
	_nav = [[UINavigationController alloc] initWithRootViewController:rootController];	

この辺りから推測なので誤りがあるかもしれないのですが、おそらく、UINavigationControllerの内部にViewControllerを配列に保持。
その配列の最後尾を画面表示しています。

その際RootViewController内のViewDidLoadが実行されて画面表示が行われています。

サンプルではこんな感じの画面を表示しています。

PushMeを押してみる

この時点ではRootViewControllerのインスタンスしか生成されていません。

PushButtonを押すとどうなるでしょうか。ボタンを押すと呼び出されるメソッドは次のようになっています。

// RootViewController.m
- (void)buttonDidPush {
	LOG_METHOD;
	
	NextViewController * nextViewController = [[[NextViewController alloc] init] autorelease];
	[self.navigationController pushViewController:nextViewController 
										 animated:YES];
}

ボタンを押した時点で初めて必要となるインスタンスを生成しています。
pushViewControllerはスタックに新しいViewControllerのインスタンスを格納すると、画面を更新します。

結果、次のように画面が更新されました。

さらにもう一度Push

Hello, next worldの下にあるボタンを押してみます。
UINavigationControllerのスタック最後尾にさらにViewControllerが追加されました。

このように配列状で遷移してきたViewControllerのインスタンスを保持しているので履歴をさかのぼる事ができます。

前の画面に戻るとインスタンスが破棄される

NavigationBarの左側にあるNext Worldを押して前の画面に戻ります。
これはUINavigationControllerの内部でpopViewControllerAnimated:を実行しています。

ログを見ると、以下のように表示されます。

2011-04-05 14:38:20.273 SampleNav[4127:207] -[LastViewController dealloc]

スタックからpopされた際に、参照先が0になります。あとはautoreleaseで自動的にインスタンスが破棄されます。

さらに前の画面に戻ってrootに戻ります。ログをみると、やはりインスタンスが破棄されています。

2011-04-05 14:38:22.656 SampleNav[4127:207] -[NextViewController dealloc]

まとめ

UINavigationControllerを使って、アプリを効率よく実行する場合は、以下のようにすると良いと思いました。

  • RootViewControllerを軽量化
  • 幹、枝となるViewControllerも軽量化
  • 葉の部分で初めて主要な機能を呼び出します。

これはiPodのように、葉の部分(楽曲情報)が多すぎるので必要になるまでインスタンス化したくないアプリで有効な気がします。

そうするとUINavigationはUITableViewControllerと親和性が高く、結果、サンプルコードはUITableViewControllerばかりになるんだと理解しました。

おしまい。