なるようになるかも

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

iOS7における20pt問題とかTabBarControllerのView領域とか。

さすがにiOS7が出て半年も経ったのにこんな問題で悩んでる人もいないよーな気もしますが。

20ptズレるのをどうするべきか

改めて書きますが、 最良の方法はAutoLayoutを使うこと です。

というか今からレイアウト組んで、iOS5を切り捨てられるなら、それ一択です。

なぜなら 今後UI要素の大きさは事前に確定しなくなる からです。

既にUISwitchUIProgressBarなどのUI要素は~iOS6/iOS7とで異なる形を取っています。

これまでは多言語化対応を迫られなければ、テキストの領域もフォントサイズから確定できましたが、DynamicTypeに対応するのであれば事前に決め打ちすることは不可能です。

iOS7 UI移行ガイドにも次のような強い表現を用いて述べられています。

あらゆるアプリケーションについて実施するべき事項

  • UI要素に関するハードコードされた値(大きさ、位置など)を見直し、システムから渡される値に基づき動的に計算するようにする。自動レイアウトの機能を利用して、レイアウト変更が必要になったとき応答できるようにする(自動レイアウトが初めてならば『Cocoa Auto Layout Guide』を参照)。

コードベースでレイアウトを組むならば、システムから渡された値を使えます。しかしStoryBoardやInterfaceBuilderでは、Intrinsic Content Size(UI要素自身が宣言する大きさのこと。Androidwrap contentと同等の概念)を取得する手段が、AutoLayout以外存在しない んです。

StoryBoardをプッシュしつつ、Deltas(AutoLayoutを切らないと使えない)を使って調整する、というのは将来性を考えると明らかに悪手で、これこそが正しいやり方と煽りながら紹介するのは正直ヒドい。

既存のレイアウトの20ptズレを吸収するにはどうすべきか

そうは言ってもAutoLayoutが使えないケースは多いです。

主となる理由はiOS5を切れない(流行に乗って初代iPad時代に業務システムを作ってしまった、過去の端末でも動作するようにして欲しい、とか)ことですが、既に組まれているxibレイアウト(UINavigationControllerを経由しないケース)を修正するのも手のかかる作業です。

20ptズレをお手軽に吸収する方法は二つあって。

  • UIScrollViewの子にしてcontentInsetで調整する
  • UIViewの階層を追加してその子にして、互換用UIViewに対してDeltasを設定する

バウンスなど無駄な挙動はOFFにした上で、20ptのcontentInsetをTopに設定するのが一番楽です。ナビゲーションバーやツールバーのすりガラス表現(個人的には全く必要性を感じない)を有効にしたい場合にも有効です。

後者の方法は、ビューコントローラーのviewプロパティ直下に、UIViewの階層を作りDeltasを設定する、というものです。コンポーネント一つ一つにDeltasを設定する人はあまり居ないと思うので、大体はこの方法に行き着くと思います。

一つ無駄なビュー階層を挟むことになりますが、XCode4以前に書いたself.view.centerのような座標を取るコードが、XCode5で齟齬を起こす問題を解消できるメリットが大きいです。

TabBarControllerのView領域

先の説明でUITabBarControllerの持つView領域についてスルーしましたが、ビルド環境がXCode4または5の場合、そしてXCode5の場合に端末がiOS~6または7の場合、それぞれ異なるView領域を持つため、やや説明が面倒なので端折りました。

XCode4についてはもはや考える必要がないので、XCode5でビルドしiOS6および7にて、UITabBarControllerview.subviewsに色とクラス名のラベルを表示したものが以下となります。

iOS6 iOS7

f:id:quesera2:20140129214510p:plain

f:id:quesera2:20140129214521p:plain

UITabBarControllerはタブバーとコンテンツコンテナの二つのView領域を持ちます。UITransitionViewがコンテンツコンテナです。

UITransitionViewはView間の遷移エフェクトを実現しているらしいプライベートなクラスです。リファレンスには載ってません。本題とほぼ関係ないので、ここでは単に「タブのコンテンツのコンテナ」程度の認識で問題ないです。

iOS6ではコンテンツのView領域とタブバーは重なり合いません。対して、iOS7では両者が重なり合う構造となっています。

つまりナビゲーションバーに対しては領域拡張によってめりこみが発生するのに対して、タブバーに対してはiOS6/7で挙動に違いがあります。ほとんどの場合、上部に20pt分マージンを作るだけで解消されるため、あまり意識しないかと思いますが…。

この構造を考慮する必要があるのは、例えばナビゲーションバーのsetNavigationBarHidden:animated:に相当するような操作を、タブバーで実現したい場合です。

iOS6ではTabBarの高さ分だけコンテンツ部のsizeを増分する必要がありましたが、iOS7ではedgesForExtendedLayoutの指定に関わらず常にタブコンテンツのコンテナ(UITransitionViewの領域)はウインドウと同じ大きさを取るため不要となります。

こういうSDKのプライベートな実装に依存したコード自体、微妙としか思えないんですが、コードスニペットが転がっていることもあって「コピペしたのはいいもののiOS7になったら動かなくなった、なんとかしてくれ」みたいな話があるんですよね…。