jarinosuke blog

about software engineering, mostly about iOS

実践 Auto Layout

今こそ frame 思考脱却の時

Xcode 4 / iOS 6 から存在していた Auto Layout でしたが、

当時は Interface Builder の Auto Layout 対応も中々ひどく、使うのが辛かった記憶があります。

そんななか僕は順調に layoutSubviews に傾倒していったわけですが、

iPhone 6/iPhone 6 plus がついに登場し、Size Class という新しい概念も投入され

現状では間違いなく2年前とは比べ物にならないレベルで Universal アプリは作りやすくなりました。(ただし iOS 8 専用アプリのみ)

ある程度のデザインパターンを懐に用意していた方が時間が省けます。

ここでは Auto Layout を用いたレイアウトに関するユースケース毎に簡潔に書いていますので、

「それ知ってるわ」みたいなのがあったら適宜読み飛ばしていって下さい。

また Auto Layout の基礎の基礎の部分は省いています。

等幅間隔で設置する

Auto Layout に少しずつ慣れてきて、今まで layoutSubviews でやってきたことをやろうとして

一番最初に「どうやるのこれ」ってなるのがこれかなと思います。

以下のリンクで僕も勉強させてもらいました。

Auto Layout は2つの View 間の制約しか設定出来ないため、等間隔を実現するとなると

間隔毎に見えない Spacer View を置いて、それらの幅を同じにする制約を張ることになります。

今までのレイアウト思考に慣れていると「えー、めちゃくちゃ余分な View 増えるな…」となりますが、それしか無いんです。しょうがない

中心から一定間隔ずらす

起動時のスプラッシュ画面も iOS 8 から nib で作成出来る様になり、

中心から50pt上にロゴを配置したいなどといった要件がある場合もあると思います。

こういった場合は Align Center Y を張った後に、その Constraint の constant を変更する事で可能です。

失敗例として自分がやっていたのは、ロゴを透明な Container View の中に置き

その Container View を Align Center Y して、その中でロゴを pin したりしていました。

UILabel を text の長さによって複数行にする

UILabel に入るテキストの長さによって、UILabel の大きさを変えたい事は多々あります。

手順を追いながら一つずつ簡単に説明します。

  • 1. numberOfLines プロパティを0に設定

numberOfLines に 0 を設定すると行数が無制限になります

  • 2. 位置を指定する Pin Constraints を設定

UILabel の size を決定する前に origin を決定します

  • 3. 高さを指定する Height Constraint を Low Prority(=250) で設定

可変の高さにしますが、height の制約は必要になります。 Priority を Low に設定する事で後述する設定が活きます。

  • 4. ContentCompressionResistancePriority(=251) より Height Constraint が低くなっている事を目視確認

Xcode 6 で確認した限りは UILabel の ContentCompressionResistancePriority はデフォルトで251となっていたので、 Low Priority の 250 よりは高くなっていると思いますが、目視で念のため確認します。

  • 5. ContentHuggingPriority(=750)の方が Height Constraint より高くなっている事を目し確認

4と同様、デフォルトで ContentHuggingPriority は750になっていると思いますが目視で確認します。

  • 6. preferredMaxLayoutWidth を設定

これを設定する事で UILabel が text から frame を計算する時の width を決定する足がかりとなります。

Xcode 6 から Automatic Preferred Max Layout Width なるものが Interface Builder に登場していますが、

残念ながら以下のリンクの通り iOS 8 以上サポートなので条件に満たさない場合はしっかり設定します。

Automatic Preferred Max Layout Width is not available on iOS version prior to 8.0

上記では簡単に説明しましたが、コードもしくは Interface Builder を用いた具体的な手順は以下のリンクが大変分かりやすいです。オススメです

UILabel sizeToFit doesn’t work with Auto Layout iOS 6

また上記で出てきた content compression resistence priority や content hugging priority、それに付随する intrinsicContentSize の説明は綺麗に省きましたが、以下のリンクが丁寧に解説されていて分かりやすいので参照下さい。

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

UIScrollView の subview と Auto Layout と contentSize

Auto Layout 使用時には contentSize をコードなどで直接セットしてはいけません。

viewDidLayoutSubviews のタイミングとかでできてしまうと思うんですが不適切です。

Auto Layout 使用時には内部の subviews の制約により contentSize が決定されるべきです。

以下のリンクの文章を参考にすると contentSize を決定する際に、

UIScrollView And Autolayout | Technical Note TN2154

contentView の Constraints は自身のサイズ(contentSize)を UIScrollView の size とは独立して計算出来る様になっていなければいけない

と解釈しました。

何が言いたかったというと UIScrollView 内の Auto Layout は結構ややこしいため、よっぽど静的で単純でない限りは未だにコードで実装しています。

以下のスライドでも UIScrollView + Auto Layout について取り上げられていますが、完全に同意です。

黒魔術AutoLayoutとiPhone 6/6 plus

Auto Layout まめ知識

Interface Builder で行う Constraint 設置作業はスポーツに近いものがあります。

どんどん出てくる warning をモノともせず、無心で Constraint をはっていくのみです。

そんなときに以下のショートカットを知っておくと少しだけクリックの負担が減ります。

  • Update Frames shift + cmd + =

  • Update Constraints opt + cmd + =

欲を言うと、 Clear Constraints も欲しかった…

終わりに

まだまだ他にも Interface Builder 上では warning 出なくなったのに

Debugger Console でひたすら Unsatisfied Constraints が出てきたりする対策方法などの

デバッギングについて紹介したかったんですが、長くなってしまったので一旦これで。

他にも何かユースケースなどあったら追加しますので教えて下さい!

参考資料