2012年12月13日木曜日

UINavigationBarをちゃんと理解する。

UINavigationBarとUINavigationItemとUIBarButtonItem

ナビゲーションバーに関わるクラスだけでこの三つもあり、名前と役割が直感的ではないので、ちゃんと区別しておきたい。

UINavigationBarは単なるバーではなく、「現在の表示」をスタックする機能を持つ。

現在の表示」はUINavigationItemとしてカプセル化されている情報のことである。i文庫のナビゲーションバーを例に取ると、


「my本棚3」がtitle、「本棚」がbackBarButtonItem、「Edit」がrightBarButtonItem、「登録は...」の文字がprompt。これらをプロパティとして持つ情報オブジェクトが、UINavigationItemである。

ここで使われていないプロパティとして、leftBarButtonItemtitleViewがある。

leftBarButtonはrightBarButton同様、ボタン要素(後述するBarButtonItemインスタンス)を設置できる。iOS5以降、left(Right)BarButtonItemsプロパティが追加され、複数個同時に設置できるようになった。ただしコードで記述する場合に限定され、InterfaceBuilderからは一つしか設置できない。

leftBarButtonItemと、backBarButtonItemはleftItemSupplementBackButonプロパティをYESにすることで同時に表示もできる。そうでない場合、setHidesBackButton:animated:メソッドでbackBarButonItemを非表示にしない限りは表示されない…と思う。

titleViewは任意のカスタムビューを表示できるビューである。デフォルトではnilで、何らかのカスタムビューと置き換えると、タイトル文字列が消えて代わりにカスタムビューが表示される。ドキュメントには「leftBarButtonItemと同時に使えない」と書いてあるけど、そんなことはなかった。またスライダーやボタンなどUI要素も使用できる。

UINavigationBarはNavigationItemをスタックする

pushNavigationItem:animated:popNavigationItemAnimated:のメソッドを使うと、UINavigationControllerで良く見慣れた「あの動き」でNavigationItemの表示を切り替えてくれる。

画面遷移を伴いナビゲーションバーを持つコンテナビューコントローラを自作したいとき、一画面内で複数のモードが切り替わるのを明示したいときなどはこのメソッドを使おう。

ナビゲーションバーごと画面遷移したり、タイトル文字が突然書き換わるのは美しさに欠ける。

残るUIBarButtonItemとは

UIBarButtonItemは、UINavigationBar、UITabBar、UIToolbarなどのバー要素で用いられるボタンをカプセル化したものである。target/actionを保持して、ボタンが押されると実行する。

ラベルとして文字か、画像のアルファチャンネル部、およびその両方を表示する。

もしボタンに表示する文字が、状況に応じて変わる可能性がある場合。文字が変わるたびにボタンのサイズも変わってしまう(あるいは文字の一部が入らなくなってしまう)のは美しくない。その対処法として、十分な幅のwidthを設定するという手もあるが、possibleTitlesプロパティを設定する手段もある。タイトルの候補となる文字列をNSSetで渡すと、そのうちで最長の表示となるものに合わせて、ボタンの長さを調整してくれる。

initWithBarButtonSystemItem:target:action:ではプリセットのボタンを選択することができる。barButtonItem同士の間隔を調整するFixibleSpaceおよびFixedSpaceもSystemItemの一つとして定義されている。

それ以外を表示したい場合、例えばフルカラーの画像やスライダーなどのコントロールを表示したい場合は、initWithCustomView:を用いる。カスタムビューモードではBarButtonItemのaction/target機構が無効になるので、ボタンなどのコントロール要素が必要であれば、UIButtonをカスタムビューに指定してBarButtonItemを生成する必要がある。

なお、リファレンスのwidthプロパティの説明文に、「ラジオモードスタイルを使用時にはこのプロパティは無視される」と書いてあるけど、唐突に出てくる「ラジオモードスタイル」とは、UITabBar下で使用することを差すのだと思われる。たぶん。

意外と知らないUINavigationBarの高さ

UINavigationBarの高さは44pxだけではない。

普通の高さ(iPhone/iPod Touchポートレート、及びiPad)は44pxで正しいのだけど、意外にレアなiPhone/iPod touchランドスケープモードでのUINavigationBarの高さは32pxになる。

そしてpromptテキストを表示すると72px(つまりprompt部は30px28px)になる。phoneランドスケープでpromptを表示させるとどうなるんでしょうか。実は調べてないので、32+30pxで62pxになる32+28pxで60pxになるのか、それより小さくなるのか分かりません…。

UINavigationControllerをrootViewControllerにしたときは、画面回転時に自動的に高さが変わるのだが、UINavigationBarをそのまま使った場合にはそうした機能は働かない。heightを処理を自分で書く必要がある。

promptの有無による高さ変化は、UINavigationItemのpromptプロパティを操作したときに自動的に行われる。promptが含まれたUINavigaitionItemをsetしても変更されない(pushやpopはOK)という点に注意。

ランドスケープモードでUINavigationBarの高さが変わると、当然UIBarButtonItemもそれに合わせて縮んでしまう。それによって用意したボタン画像のサイズが不恰好になる場合には、landscapeImagePhoneプロパティに32pxのバーに最適化した画像を設定しておくと、自動的に切り替えてくれる。initWithImage:landscapeImagePhone:style:target:action:というUIBarButtonItemの生成メソッドも用意されている。

InterfaceBuilderには頼れない部分

このUINavigationBar周りは、InterfaceBuilderでは出来ないことが多い。rightBarButtonに複数のアイテムを加える、UIBarButtonItemとしてカスタムビューを使う(なぜかUISegmentedControlのみ可)など。

それはUINavigationControllerを使い、コンテンツビューはIBで配置しつつ、TabBarButtonは生成も含めてコードに記述することを想定しているからではないだろうか。静的なコンテンツに対して、ナビゲーションは画面の動的な部分(画面遷移や、編集などモードの切り替え)を担当するので、その方が合理的に思える。

まとめ
  • UINavigationBarは、UINavigationItemをスタックしてる。
  • UINavigationItemはタイトルやボタンなど、バーの表示をカプセル化してる。
  • UIBarButtonItemはバー系で使うボタンを共通化してる。
  • この辺りを理解するとUINavigationControllerについて理解が深まる。
  • コードでしかできないこと、IBでもできることについて理解しよう。

2 件のコメント:

  1. prompt部は、72px-44pxなら28pxではありませんか?

    返信削除
  2. ご指摘ありがとうございます!恥ずかしい計算ミスでした…。

    返信削除