UIAppearanceとUIBarPositionとバグと
iOS7/6対応を考えたとき、もっとも面倒なのはナビゲーションバーの画像を用意することのように思う。
まずPortraitとして、44px、88px(@2x)、128px(iOS7)、同様にLandscapeとして32px、64px、104pxの合計6種類の画像が必要になる。
非retinaであるiPhone3GSがiOS7に対応していた場合、ここにさらに2種類増えたことになる。恐ろしい。
iPadには非retinaでもiOS7へアップデートできるものがあるが、iPadではUINavigationBar
が、デバイスの向きに応じて高さを変えることはないので、Landcape用の高さの画像を用意する必要はない。
まぁ、その代わりiOS6/7でUITabBar
の高さが違うので、4種類は必要になるんだけども。
正直ステータスバー部分の色などどうでもいいので、128pxと104pxを切りたいと考えた。
しかしこれはあまりよろしくないようだ。まずステータスバーの色は強制的に 黒固定 になってしまう上こと、そして文字色まで黒になるバグがあるからだ。
UIBarPosition
iOS7 UI 移行ガイドには、ナビゲーションバーの背景画像に44ptの高さの画像を指定した場合の挙動について、
UIBarPositionTopAttachedを指定している場合は黒。
UIBarPositionTopを指定している場合はウインドウの背景が透けて見える。
と説明されているが、ウインドウの背景が透けて見えるパターンは、実際には存在しない。
ほとんどの場合、UIBarPosition
自体意識しないと思うので、その存在意義について説明すると。
もしInterfaceBuilderやコードによるレイアウトで、UINavigationController
を経由せずにUINavigationBar
を表示させた場合、そのナビゲーションバーはステータスバーにめり込んでしまい、一体化しない。
当該のUIViewController
をUIBarPositioningDelegate
に準拠させ、positionForBar:
メソッドを実装し、UIBarPositionTopAttached
を返却するようにすると、ナビゲーションバーとステータスバーを一体化させることができる。
もっとも、ほとんどの場合、単純にUINavigationController
を経由させた方が早い。UINavigationController
を経由したUINavigationBar
は、暗黙のうちにUIBarPositionTopAttached
が指定されている。
このため、UIBarPositioningDelegate
が必要とされる状況は極めて特殊なケースになると思われる。この周りを理解しないで作られた既存コードの改修だとか、利用している化石モノのSDKをiOS7対応のために独自に改造しなければいけないとか。
InterfaceBuilderとStoryBoard
IBもしくはコードレイアウトにて、と注釈をつけたのには意味があり、StoryBoardで生成したUIViewController
では、UIBarPositioningDelegate
に準拠しても何も起こらない。
IBやコードベースのレイアウトには、「そのUIViewController
が何を経由し、どう表示されるのか分からない」という欠陥がある。
例えばナビゲーションバーを経由し領域拡張を利用する場合、原点とすべき座標が0ptまたは64ptもしくは52ptとなることを考慮し、適切にレイアウトを組むなり、contentInsets
を設定するなりよろしくやる必要がある。
対するSBは、デシリアライズされるビュー階層が、どのようなコンテキストで表示されるのかを記述できる強力なアドバンテージがある。
もしSBで組んだレイアウトが、デシリアライズされた後にUIBarPositioningDelegate
の指定によって表示を変えるのならば、それはSBにコンテキストを記述する意義を否定しているため、敢えて無視されるのだ…と考えると一応は納得はできる。
実際の意図はどうなのか分からないけど。
UINavigationControllerとUIBarPosition
そして本題なのだけど、UINavigationController
を経由して表示しているUINavigationBar
は UIBarPositionTopAttached
が指定されると書いたけど、厳密には UIBarPositionTopAttached
から変更することができない という書き方が正しい。
まずnavigationBar
プロパティはUINavigationController
によって管理されているため、delegate
を変更した時点でその旨のエラーログを吐いてクラッシュする。positionForBar:
による変更は不可能である。
そこでsetBackgroundImage:forBarPosition:barMetrics:
のbarPosition
の指定に意味があるのかと考えたのだけど、UIAppearance
経由/直接指定に関わらず、44ptの背景画像をセットした場合には、UIBarPositionTopAttached
扱いとなるため、ステータスバーの背景は黒固定になる。
バグについて
iOS7.0.4現在、44ptの画像を設定した場合に、ステータスバーの背景が黒固定になるにも関わらず、文字色も黒になるバグがある。
どうもUITabBarController
による切り替え時に、UIStatusBarStyleDefault
のままでは、文字色の判定に失敗するらしい。自分はUIAppearance
経由での設定を好むので、これが発生条件の一つに絡むのかもしれない。
ともあれ、明示的にUIStatusBarStyleLightContent
を指定するなり、素直に64ptの画像を用意するすれば解決できるので致命的ではないものの、44ptの画像を使いまわして楽をしたかったという魂胆は脆くも崩れ去ったのであった。
UIBarPositionTopを指定している場合はウインドウの背景が透けて見える
これが本当だったならばなぁ。