AutoLayoutは「Constraint(制約)」を課すことで、レイアウトを維持する方法です。
例えば、親Viewの端から10px離す、みたいなことを定義していきます。
こちらの記事(iOSのAutoLayoutの基本的な記述方法とエラーメッセージの種類)を参考にさせて頂きました。
Storyboardを使っている場合はGUIで設定するのが基本だと思いますが、コードから生成するViewがまったくない、ということもあまりないでしょう。
その際にも、複雑なレイアウトのための計算をしないで済むように、Constraintを適切に定義することで画面サイズによらないレイアウトが可能です。
iPhone6から画面サイズが変わると言われているし、iPadに至っては画面分割も出てくるかもしれません。
その際に、色々な条件を考慮して数値的に計算するのはだいぶきびしくなっていくと思います。
今後はやはりAutoLayout必須になっていくでしょう。
サンプルコード
以下に、画面の上下左右にぴったりフィットする制約のサンプルコードを載せます。
※ちなみに、constant
に値を入れるとその分、距離が離れるようになります。
また、右端などから離す場合はマイナスの値を入れる必要があるようです。
NSLayoutConstraint *layoutTop =
[NSLayoutConstraint constraintWithItem:aView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
// --- 中略 ---
NSArray *layoutConstraints = @[layoutTop,
layoutBottom,
layoutLeft,
layoutRight];
// AutoResizingMaskでのレイアウトをオフにする
[aView setTranslatesAutoresizingMaskIntoConstraints:NO];
// 生成したレイアウトを配列でまとめて設定する
[self.view addConstraints:layoutConstraints];
NSLayoutConstraint
クラスがConstraintを表現するクラスです。
Constraint生成時に渡している2つのビューは、それぞれがどういう関係になるかを示すものです。
(記事冒頭の、「親Viewの端から10pxうんぬん」の話)
constraintWithItem
とtoItem
に渡すビューはどちらが先でも、相互の関係の定義なので問題ないようです。
ちなみに参考にさせてもらった記事から注意点を引用すると
- 指定するViewは先にaddSubviewで追加されている必要がある。
- 2つのattribute引数で指定できる組み合わせは決まっている。(例:NSLayoutAttributeTopとNSLayoutAttributeBottomはOKだが、NSLayoutAttributeTopとNSLayoutAttributeLeftはNG等)
- 第一引数とtoItem引数で指定できるViewの順番は逆でも可能だが、統一しておいた方がわかりやすい。
- attribute引数で単純に横幅(NSLayoutAttributeWidth)や高さ(NSLayoutAttributeHeight)を指定する場合は、対になるViewはnilにする。
とのこと。
iOS6から使えるのでiOS5対応がいらなければ積極的に使っていきたいですね。
[追記]
constraintsWithVisualFormat:options:metrics:views メソッドを使ってレイアウトする
コメントで、「Visual Format Language」によるレイアウトについて指摘頂きました。
「Language」と名がつく通り、NSStringでテキストを渡して柔軟にレイアウト処理を書ける簡易言語のようです。
なにはともあれコードを見てもらうとなにかが分かると思います。
サンプルコード
// Create constraints
self.childView.translatesAutoresizingMaskIntoConstraints = NO;
NSNumber *vPadding = @0;
NSNumber *hPadding = @0;
UIView *childView = self.childView;
NSDictionary *views = NSDictionaryOfVariableBindings(childView);
NSDictionary *metricsDictionary = NSDictionaryOfVariableBindings(vPadding, hPadding);
NSArray *vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-vPadding-[childView]-vPadding-|"
options:0
metrics:metricsDictionary
views:views];
[self.view addConstraints:vConstraints];
NSArray *hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-hPadding-[childView]-hPadding-|"
options:0
metrics:metricsDictionary
views:views];
[self.view addConstraints:hConstraints];
[self.view layoutIfNeeded];
NSDictionaryOfVariableBindingsマクロで辞書を生成
どうも実装を見ると、可変引数で受け取った変数名をキーに、その参照を値にしてくれるマクロのようです。
サンプルコードではビューの辞書と、metricsの辞書のふたつを生成しています。
※ ちなみに、self.childView
をchildView
に入れていますが、ドットが含まれるとエラーになってしまったので、いったんローカル変数に入れてから辞書を生成しています。
NSStringで柔軟にレイアウト
formatを見てもらうとなんとなく分かるかと思います。(@"V:|-vPadding-[childView]-vPadding-|"
)
レイアウト対象となるビューは[childView]
として、[]
で囲みます。
また、vPadding
やhPadding
は、metricsDictionaryで定義されているキーです。
今回の例で言えばどちらも0
が設定されます。
また、テキストの頭にあるV
やH
は「vertical」と「horizontal」の頭文字です。
つまり左右方向なのか上下方向なのか、ということですね。
そして|
が隣接する要素や画面端などの「端」を表します。
辞書をうまく使えばメンテナンス性もあがり、結構柔軟にレイアウトを定義できそうな感じですね。
ハマりポイント
AutoLayoutを実装していると以下のようなエラーメッセージが出てクラッシュする場合があります。
The view hierarchy is not prepared for the constraint ...
一番ありがちな問題としては、Constraintを設定しようとしているビューたちがまだどのビューにもaddSubview
されていない場合に起こります。
その場合はまず最初にビューを必ずなにかしらのビューに属するようにしてください。
設定したConstraintsは親に設定する
自分がハマったのはこちらでした。
なんとなく、設定したいビューに対してaddConstraint
したくなりますが、すべき対象は、AutoLayoutしたい要素を内包している一番TOPにある親ビューです。
例えば、parent, A, Bという3つのビューがあり、AとBに対してAutoLayoutを指定した場合でも、addConstraint
を実行するのはparent
に対してになります。
そうしないと実行時にクラッシュします。
translatesAutoresizingMaskIntoConstraints
はNO
に
UIViewにはtranslatesAutoresizingMaskIntoConstraints
というプロパティがあります。
AutoLayoutで制御したいビューの上記プロパティをNO
にしないと、クラッシュこそしないもののコンソールにエラーが出力されるので注意が必要です。