俺#

新潟市でIT業を営むおっさんのブログ。

UINavigationControllerでハマった話

iOSで画面遷移を司るUINavigationControllerのお話。

UINavigationControllerは複数のUIViewControllerを切り替えて表示するための仕組みを提供してくれる。画面遷移するコードは以下のような感じ。

NextViewController* v = [ [ [NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil] autorelease];
[appDelegate.navigationController pushViewController:v animated:YES];

appDelegateはアプリケーションのデリゲートのインスタンス、navigationControllerはUINavigationControllerのアウトレットの紐付け先である。UIViewControllerはスタック状に積み上げられるので、popToViewControllerすると前の画面に戻る事ができる。

で、ここからが本題。

UINavigationControllerは画面の上部にUINavigationBarと呼ばれる領域を表示してくれる。左側が矢印になった「戻る」ボタンなどが配置される、ほとんどのiOSアプリで表示されるタイトルバー的なアレ。

このUINavigationBar、左右に配置するボタンを動的に変更できたりする。例えば右側に「次へ」ボタンを表示するコードはこんな感じ。

UIBarButtonItem* next = [ [ [UIBarButtonItem alloc] initWithTitle:@"次へ" style:UIBarButtonItemStylePlain target:self action:@selector(nextBtnClick)] autorelease];
[viewController.navigationItem setRightBarButtonItem:next animated:NO];

viewControllerはUIViewController(の継承クラス)のインスタンス、navigationItemはUIViewControllerに定義されているUINavigationBar型のプロパティである。

ここで1つワナがある。UINavigationControllerにもUINavigationBar型のnavigationItemというプロパティがあり、最初に表示されている画面においては、これを使用してボタンの追加や変更が可能なのである。ところが、2番目以降の画面においては、UINavigationControllerのnavigationItemを操作してもうまくいかないのだった。必ず現在の画面のUIViewControllerのnavigationItemプロパティを使用する必要がある。

内部的にUIViewController毎にUINavigationBarを生成しているのかもしれないが、それにしてはsetRightBarButtonItemやsetLeftBarButtonItemは動かない*1のに、setBackBarButtonItemだけは使えてしまう挙動は謎い。アプリケーションのデリゲートの参照をどこかにもって使いまわすような実装をしていると、デリゲートのプロパティを使用してしまいがちなので注意したい。

まぁ、UIViewControllerの継承クラスであるUINavigationControllerへの参照をUIViewControllerが持っている、という時点でOOP的にどうよ?と思うのだが(UINavigationControllerが複数のUIViewControllerを内包する構造を考えてもスッキリしない)、きっと何か実装上の問題があって実を取ったのだと理解しておこう。

*1:正確には初期画面のナビゲーションバーに対しての操作になってしまう。