iOS7における20pt問題とかTabBarControllerのView領域とか。
さすがにiOS7が出て半年も経ったのにこんな問題で悩んでる人もいないよーな気もしますが。
20ptズレるのをどうするべきか
改めて書きますが、 最良の方法はAutoLayoutを使うこと です。
というか今からレイアウト組んで、iOS5を切り捨てられるなら、それ一択です。
なぜなら 今後UI要素の大きさは事前に確定しなくなる からです。
既にUISwitch
やUIProgressBar
などのUI要素は~iOS6/iOS7とで異なる形を取っています。
これまでは多言語化対応を迫られなければ、テキストの領域もフォントサイズから確定できましたが、DynamicTypeに対応するのであれば事前に決め打ちすることは不可能です。
iOS7 UI移行ガイドにも次のような強い表現を用いて述べられています。
あらゆるアプリケーションについて実施するべき事項
- UI要素に関するハードコードされた値(大きさ、位置など)を見直し、システムから渡される値に基づき動的に計算するようにする。自動レイアウトの機能を利用して、レイアウト変更が必要になったとき応答できるようにする(自動レイアウトが初めてならば『Cocoa Auto Layout Guide』を参照)。
コードベースでレイアウトを組むならば、システムから渡された値を使えます。しかしStoryBoardやInterfaceBuilderでは、Intrinsic Content Size
(UI要素自身が宣言する大きさのこと。Androidのwrap 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にて、UITabBarController
のview.subviews
に色とクラス名のラベルを表示したものが以下となります。
iOS6 | iOS7 |
---|---|
UITabBarController
はタブバーとコンテンツコンテナの二つのView領域を持ちます。UITransitionView
がコンテンツコンテナです。
UITransitionView
はView間の遷移エフェクトを実現しているらしいプライベートなクラスです。リファレンスには載ってません。本題とほぼ関係ないので、ここでは単に「タブのコンテンツのコンテナ」程度の認識で問題ないです。
iOS6ではコンテンツのView領域とタブバーは重なり合いません。対して、iOS7では両者が重なり合う構造となっています。
つまりナビゲーションバーに対しては領域拡張によってめりこみが発生するのに対して、タブバーに対してはiOS6/7で挙動に違いがあります。ほとんどの場合、上部に20pt分マージンを作るだけで解消されるため、あまり意識しないかと思いますが…。
この構造を考慮する必要があるのは、例えばナビゲーションバーのsetNavigationBarHidden:animated:
に相当するような操作を、タブバーで実現したい場合です。
iOS6ではTabBarの高さ分だけコンテンツ部のsizeを増分する必要がありましたが、iOS7ではedgesForExtendedLayout
の指定に関わらず常にタブコンテンツのコンテナ(UITransitionView
の領域)はウインドウと同じ大きさを取るため不要となります。
こういうSDKのプライベートな実装に依存したコード自体、微妙としか思えないんですが、コードスニペットが転がっていることもあって「コピペしたのはいいもののiOS7になったら動かなくなった、なんとかしてくれ」みたいな話があるんですよね…。