なるようになるかも

力は多くの場合、その人の思いを超えない。

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を表示させた場合、そのナビゲーションバーはステータスバーにめり込んでしまい、一体化しない。

当該のUIViewControllerUIBarPositioningDelegateに準拠させ、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を経由して表示しているUINavigationBarUIBarPositionTopAttachedが指定されると書いたけど、厳密には UIBarPositionTopAttachedから変更することができない という書き方が正しい。

まずnavigationBarプロパティはUINavigationControllerによって管理されているため、delegateを変更した時点でその旨のエラーログを吐いてクラッシュする。positionForBar:による変更は不可能である。

そこでsetBackgroundImage:forBarPosition:barMetrics:barPositionの指定に意味があるのかと考えたのだけど、UIAppearance経由/直接指定に関わらず、44ptの背景画像をセットした場合には、UIBarPositionTopAttached扱いとなるため、ステータスバーの背景は黒固定になる。

バグについて

iOS7.0.4現在、44ptの画像を設定した場合に、ステータスバーの背景が黒固定になるにも関わらず、文字色も黒になるバグがある。

どうもUITabBarControllerによる切り替え時に、UIStatusBarStyleDefaultのままでは、文字色の判定に失敗するらしい。自分はUIAppearance経由での設定を好むので、これが発生条件の一つに絡むのかもしれない。

ともあれ、明示的にUIStatusBarStyleLightContentを指定するなり、素直に64ptの画像を用意するすれば解決できるので致命的ではないものの、44ptの画像を使いまわして楽をしたかったという魂胆は脆くも崩れ去ったのであった。

UIBarPositionTopを指定している場合はウインドウの背景が透けて見える

これが本当だったならばなぁ。