AutoLayoutをコードで書いてみる
つみきでアプリエンジニアをしている野田です。
iPhone6とiPhone6 Plusの登場で、iPhoneでも様々な画面サイズに対応しなくてはならなくなってしまいました。この画面サイズのレイアウト対応にAutoLayoutというものがあります。ただAutoLayout、なかなか癖があり厄介な部分もあります。Storyboard上でGUIを使い簡単に設定できるようになってはいますが、最初からViewのサイズ等が決まっていないとなかなか簡単に実装することができません。
そこで別の手段としてコード上で直接書いてしまう方法があるので今回はその方法について紹介したいと思います。
NSLayoutConstraintを使う
上記クラスを使うことで、コード上でAutoLayoutの制約をView同士に与えることができます。
制約を作成する方法は
- constraintsWithVisualFormat:options:metrics:views:
- constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
の主に2つの方法があります。
大きな違いはVisual Format Languageを使うかどうかだと思います。
それでは、以下の画像に示すような白い親Viewに対して上下左右50Pixelずつインセットを与えた青い子Viewの場合に、コード上でAutoLayoutの制約を課す方法をそれぞれについて例をあげながら説明します。
constraintsWithVisualFormat:options:metrics:views:
Visual Format Languageを用いて制約を作成していきます。
Visual Format LanguageについてはAuto Layout Guideに記載されています。
ざっくりlayoutを文字列で書けるようになったものだと考えていいと思います。
以下に、実際に制約を与えるコードを示します。
UIView *view1 = [[UIView alloc] initWithFrame:CGRectZero];
view1.backgroundColor = [UIColor blueColor];
view1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:view1];
NSNumber *inset = @50;
NSDictionary *metricsDictionary = NSDictionaryOfVariableBindings(inset);
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(view1);
NSArray *verticalConstraints =
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-inset-[view1]-inset-|"
options:0
metrics:metricsDictionary
views:viewsDictionary];
[self.view addConstraints:verticalConstraints];
NSArray *horizontalConstraints =
[NSLayoutConstraint constraintsWithVisualFormat:@"|-inset-[view1]-inset-|"
options:0
metrics:metricsDictionary
views:viewsDictionary];
[self.view addConstraints:horizontalConstraints];
上記のように書くことで親Viewに対して上下左右に50Pixelインセットした子Viewの制約を課すことができます。この方法の場合一度にいくつかのNSLayoutConstraintを返すことができるので戻り値はNSArray型となっています。
実際のviewと文字列のviewを紐付けるにはviewsパラメータに
NSDictionary *viewsDictionary = @{@"view1" : view1オブジェクト}
とdictionaryで与えてあげます。
上記のプログラムではこれを簡単にするためにNSDictionaryOfVariableBindingsというマクロを使って変数名をキー、その値をオブジェクトとしています。
また、間隔のPixel値を変数として与えたい場合はmetricsの部分に上記のように変数をdictionaryとしたものを与えることで実現できます。
コードで制約を課す場合の注意点として
view1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:view1];
制約を与えるviewのtranslatesAutoresizingMaskIntoConstraintsをNOに、また制約を与える前にaddsubviewしておく必要があります。
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
こちらは1つずつNSLayoutConstraintを作成していく方法になります。
個人的にはこちらのほうがわかりやすく自分で書く際にはこっちの方法を主に使っています。
Visual Format Languageを使った場合と同様の制約を与えるコードを以下に示します。
UIView *view1 = [[UIView alloc] initWithFrame:CGRectZero];
view1.backgroundColor = [UIColor blueColor];
view1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:view1];
CGFloat inset = 50;
NSLayoutConstraint *topConstraint =
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:inset];
NSLayoutConstraint *bottomConstraint =
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-inset];
NSLayoutConstraint *leftConstraint =
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:inset];
NSLayoutConstraint *rightConstraint =
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:-inset];
NSArray *constraints = @[
topConstraint,
bottomConstraint,
leftConstraint,
rightConstraint
];
[self.view addConstraints:constraints];
上記のようになります。
こちらの場合、itemとtoItemに対して他のパラメータで制約を決めていきます。attributeはそれぞれのどこに対して制約を課すかを示します。例えばtopConstraintは子Viewの上端が親Viewの上端に対して50pixelの間隔をあけるという事を示しています。また、toItemに対してという事になるのでbottomConstraintではconstantがマイナスの値になります。
だけど、コードで書くのめんどくさい
上で述べたようにコードでAutolayoutを書くことは可能です。
が、どちらの方法も長くなりがちでめんどくさいです。
そこで、このめんどくさいコードを簡単に記述できるライブラリを最後に紹介します。
それが、Masonryです。
このライブラリを使うと先ほどの制約が
UIEdgeInsets inset = UIEdgeInsetsMake(50, 50, 50, 50);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view).width.insets(inset);
}];
なんとこれだけで書けちゃいます。
コードの量も減りUIEdgeInsetsとして書けるのでよりわかりやすく書けますね。
もちろんCocoaPodsにも対応しています。AutoLayoutをコードで書く場合には導入を検討してもいいと思います。
まとめ
今回は、AutoLayoutをコードを使って書く方法を紹介しました。
コードで書けるため動的にViewのサイズが変更される場合には知っておいて損はない手法だと思います。
今後のさらなるサイズ拡張やiPadに対応したユニバーサルなアプリを作る際にはAutoLayoutは必須になっていくと思うのでもっと深く勉強し、理解を深めていきたいです。