[iOS 7] Xcode 5 で始める Auto Layout 入門 #6 – 補足編

2013.09.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

補足事項まとめ

今回は、前回まで説明してきた内容の補足事項についてまとめたいと思います。

iOS 6 以前のデバイスでの表示互換性

iOS 6 以前のデバイスでの表示を修正する

前回作成したレイアウトを iOS 6 以前のデバイスで表示してみましょう。すると、下図のように上側の UIView とステータスバーの間に間隔がありません。

xcode5-autolayout01-069

iOS 7 では、ステータスバーは透明でコンテンツ部に覆い被さるものに変更されました。そのため、このレイアウトで問題なかったのですが、iOS 6 ではこのレイアウトは不自然です。

この問題を修正しましょう。

制約を再設定する

まず、上側の UIView に設定されている親ビューの上端との間隔に関する制約を選択して削除します。制約の削除は、制約を選択後、delete キーを押下することでできます。

xcode5-autolayout01-070

次に、再度親ビューの上端との間隔に関する制約を追加します。上側の UIView を選択して Pin メニューを表示します。Spacing to nearest neighbor で上側の相対位置の制約を選択します。この際、"0" という数字が表示されているテキストインプットの右端にあるアイコンを押下してメニューを表示して下さい。このメニューの下側の項目では、デフォルトでは "Top Layout Guide" が選択されていると思いますが、"View" を選択します。

xcode5-autolayout01-071

Top Layout Guide と View

メニューに出てきた "Top Layout Guide" と "View" という項目は、「上側にある最も近いコンポーネント」の定義を変更するオプションで、実はデフォルトでは Top Layout Guide が「上側にある最も近いコンポーネント」として扱われていました。今回は NavigationBar がないので、Top Layout Guide はステータスバーの下端の位置を表しています。さらに、"current distance = 0" と表示されていることからも分かりますが、このオプションで適用する制約は Top Layout Guide から 0pt の位置を表しています。つまり、ステータスバーの下端の位置に対象コンポーネントの上端の位置を固定する制約になってしまっていたということです。

一方、View は親ビューの上端を制約の適用対象として扱います(ここでの親ビューとは、この UIViewController のルートビューのことを指します)。このため、このオプションであれば、親ビューの上端との間隔を表現することができます。さらに、"current distance = 20" と表示されている通り、適用された制約は親ビューの上端から 20pt の位置を表現する制約となります。

この違いが、iOS 7 以降と iOS 6 以前での表示結果に差を生じさせます。これは、iOS 6 ではルートビューの frame がデフォルトではステータスバーの下端位置から開始していたのに対し、iOS 7 ではスクリーン上端から開始し、ステータスバーの領域を含んでいることに起因します。

今回、制約選択時に View を選択したのは、あくまで親ビューの上端との間隔を制約として表現したかったためです。こうすることで、iOS 7 ではスクリーンの上端から 20pt 下の位置、iOS 6 ではステータスバーの下端から 20pt 下の位置に UIView の上端位置を固定することができます。

制約を適用すると垂直方向の相対位置に関する制約が追加されます。また、Canvas 上の制約アイコンが先ほどと異なっています。

xcode5-autolayout01-072

では、アプリを実行してレイアウトを確認してみましょう。まず、iOS 7 で実行してみます。

xcode5-autolayout01-073

特に変化はありません。次に iOS 6 で実行してみます。

xcode5-autolayout01-074

ステータスバーとの間に間隔ができ、自然なレイアウトになりました。

UINavigationController を利用しない場合は注意

UINavigationController を利用している場合は、ステータスバーが透明であることを前提とするレイアウトは基本的には組まないかと思います。このため、Top Layout Guide を基準とした制約を利用しても iOS 6 との間の表示互換性に問題は生じないと思います。しかし、今回のように UINavigationBar がない画面のレイアウトを作成する場合は注意が必要です。

Intrinsic Content Size

UIButton, UILabel など一部の UI コンポーネントは、自身の推奨サイズを UIView で定義された intrinsicContentSize メソッドから取得できます。例えば、ラベル表示のみの UIButton であれば、内部のテキストの幅や高さにパディングを加えたものが推奨サイズとなります。また、内部のコンテンツの大きさが変化した場合、推奨サイズも追従して変化します。

Auto Layout では、UI コンポーネントの位置や幅、高さを決定するための制約が不足している場合にこの推奨サイズを利用してレイアウトを決定します。

推奨サイズを持たない UI コンポーネント

下図は、推奨サイズを持たない UIView を Interface Builder 上でレイアウトした場合の制約を示しています。

xcode5-autolayout01-007

UIView の位置とサイズを決定する為に、以下の4つの制約が設定されています。

  • コンテナ(親ビュー)内で水平方向にセンタリングする制約
  • コンテナ内で垂直方向にセンタリングする制約
  • コンテナの左端と自身の左端との距離を固定する制約
  • コンテナの上端と自身の上端との距離を固定する制約

これら4つの制約によって、UIView の水平位置・垂直位置・幅・高さを決定する事ができました。

推奨サイズを持つ UI コンポーネント

一方、こちらは推奨サイズを持つ UIButton を Interface Builder 上でレイアウトした場合の制約を示しています。

xcode5-autolayout01-008

こちらについては、以下の2つの制約のみしか設定していません。

  • コンテナ内で水平方向にセンタリングする制約
  • コンテナ内で垂直方向にセンタリングする制約

これらの制約だけでは、UIButton の中心位置しか決定することができず、幅や高さは本来決定することができないはずです。しかし、UIButton の推奨サイズから Auto Layout が幅と高さを決定してくれます。結果、2つの制約を追加するだけで UIButton の水平位置・垂直位置・幅・高さを確定させる事ができます。

Priority

Content Hugging Priority

Content Hugging Priority は、推奨サイズを持つコンポーネントがその推奨サイズと他の制約との間で設定が矛盾してしまった場合の制約適用の優先順位を決定します。簡単に言えば、他の制約との関係によって推奨サイズより大きくなる際の大きくなりづらさを決める値です。

具体的な例を見てみましょう。下図は、UILabel を配置した上で、親ビューの左端・右端との間隔を 20pt にする制約を適用した状態です。ここでさらに、上側の UILabel のみ制約の Priority を 250 に変更しています。下側の UILabel の制約の Priority はデフォルト値の 1000 です。

xcode5-autolayout01-086

UILabel の Content Hugging Priority のデフォルト値は 251 です。このため、制約の Priority より Content Hugging Priority が高い上側の UILabel では、推奨サイズがそのまま適用されます。一方、制約の Priority より Content Hugging Priority が低い下側の UILabel では、制約が優先されて推奨サイズより大きい幅が UILabel に適用されます。

Content Compression Resistance Priority

Content Compression Resistance Priority も、推奨サイズを持つコンポーネントがその推奨サイズと他の制約との間で設定が矛盾してしまった場合の制約適用の優先順位を決定します。こちらは、他の制約との関係によって推奨サイズより小さくなる際の小さくなりづらさを決める値です。

具体的な例を見てみましょう。下図は、UILabel を配置した上で、親ビューの左端・右端との間隔を 120pt にする制約を適用した状態です。ここでさらに、上側の UILabel のみ制約の Priority を 250 に変更しています。下側の UILabel の制約の Priority はデフォルト値の 1000 です。

xcode5-autolayout01-087

UILabel の Content Compression Resistance Priority のデフォルト値は 750 です。このため、制約の Priority より Content Compression Resistance Priority が高い上側の UILabel では、推奨サイズがそのまま適用されます。一方、制約の Priority より Content Compression Resistance Priority が低い下側の UILabel では、制約が優先されて推奨サイズより小さい幅が UILabel に適用されます。

まとめ

複数回に渡って Auto Layout の概要を説明してきました。少しでも Auto Layout について理解していただけたのであれば幸いです。

これまでの記事で紹介してきた機能が、Auto Layout の全てではありません。より詳しく Auto Layout について知りたい方は、NSLayoutConstraint といった Auto Layout の実体である API 群について把握することをおすすめします。幸い、Web や書籍など詳しく解説されているソースをすぐに見つけることができると思います。