Auto Layoutはプログラムでも実現できます。Visual Format Languageと呼ばれるものを使います。今回はその説明ですが、あらかじめ言っておくと、自分はプログラムでAuto Layoutを実装することはやめたほうがいいと思っています。最後にその理由を説明しますが、何が駄目かを知るためにもVisual Format Languageについて説明しましょう。

サンプルコードを元に説明します。

https://github.com/stack3/STVisualFormatLanguageSample

まずは、おさらい。Auto LayoutはViewどうしの相対的な配置制約にもとづいてレイアウトが実現されます。このViewどうしの制約のことをConstraintと呼びます。プログラムによるAuto Layoutは、このConstraint(NSLayoutConstraint)を作り、Superviewに追加することで実現します。

サンプルを起動しましょう。

01

Visual Format Languageを選択すると以下の画面が表示されます。

02

ButtonなどのSubviewが並んでいますが、すべてプログラムによって配置しています。

STVisualFormatLanguageViewController.mのviewDidLoadで配置処理を行なっています。

まずはbutton1について。

UIButton *button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button1.translatesAutoresizingMaskIntoConstraints = NO;
[button1 setTitle:@"Button1" forState:UIControlStateNormal];
[self.view addSubview:button1];

プログラムでAuto Layoutするときは、そのViewのtranslatesAutoresizingMaskIntoConstraintsプロパティをNOにする必要があります。

button1のConstraintの生成。

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-8-[button1]-8-|"
                                                      options:0
                                                      metrics:nil
                                                      views:viewsDictionary];
[self.view addConstraints:constraints];

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-8-[button1]"
                                                      options:0
                                                      metrics:nil
                                                        views:viewsDictionary];
[self.view addConstraints:constraints];

“|-8-[button1]-8-|”、”V:|-8-[button1]”という文字列がVisual Format Languageです。

button1のために以下を満たすConstraintを生成する必要があります。

  •  Superviewの上端、左端、右端の間隔を8px固定

Constraintを生成するためのVisual Format Languageは以下のようになっています。

  • “|-8-[button1]-8-|”は、Superviewとの左右の間隔を8px固定
  • “V:|-8-[button1]”は、Superviewと上端との間隔を8px固定

V:を先頭につけることで垂直方向(Vertical)の制約であることを示します。

Visual Format Languageでの”button1″という文字列と変数button1のひも付けは、viewDictionary変数によって行われています。この場合、viewDictionary変数は、key: “button1″、object: button1が格納されています。これをプログラムで書く手間を省くために、NSDictionaryOfVariableBindingsというマクロが用意されています。通常はこれを使ってNSDictionaryを生成します。

NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(button1, button2, searchTextField, searchButton);

これでviewsDictionaryにSubivewの変数名とSubview変数の組み合わせが格納されます。

次にbutton2について。

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-widePadding-[button2]-widePadding-|"
                                                      options:0
                                                      metrics:metricsDictionary
                                                      views:viewsDictionary];
[self.view addConstraints:constraints];

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[button1]-padding-[button2]"
                                                      options:0
                                                      metrics:metricsDictionary
                                                      views:viewsDictionary];

button2のために以下を満たすConstraintを生成する必要があります。

  • Superviewの左端、右端の間隔を20pxで固定
  • button1の下に8pxの間隔を固定して配置

Visual Format Languageは以下のようになっています。

  • “|-widePadding-[button2]-widePadding-|”は、Superviewとの間隔を20pxで固定
  • “V:[button1]-padding-[button2]”は、button1との間隔を8pxで固定

“widePadding”、”padding”は、それぞれ20px、8pxを示しています。NSLayoutConstraint#constraintsWithVisualFormatの引数metrics(NSDictionary)に文字列と数値(NSNumber)の組み合わせを渡すことで、文字列でピクセル値を指定することが可能になっています。ここもNSDictionaryOfVariableBindingsを使っています。

NSNumber *padding = @8;
NSNumber *widePadding = @20;
NSDictionary *metricsDictionary = NSDictionaryOfVariableBindings(padding, widePadding);

Visual Format Languageで”|-20-[button2]-20-|”のように、直接数値を指定するよりは変数で指定できる方が保守性が高くなるでしょう。

ちなみに、以下のように値を指定しないとデフォルトの間隔になります。

  • “|-[button]-|”  Superviewとの間隔はデフォルト20px
  • “|button1|-|button2|-|button3|”  Subviewどうしの間隔はデフォルト8px

これらのデフォルト値はInterface Builderを使って配置した時に自動補完される間隔と同じです。特にSubviewどうしの間隔は通常はデフォルトの8pxのままのほうが良いでしょう。よっていちいち値を指定する必要はないと思います。

最後のsearchTextFieldとsearchButtonの組み合わせです。以下のConstraintを生成する必要があります。

  • searchTextFieldとSuperview左端の間隔は20px(デフォルト値)固定
  • searchTextFieldの幅は可変
  • searchButtonとSuperview右端の間隔は20px(デフォルト値)固定
  • searchButtonの幅は100px固定、高さは30px固定
  • searchTextField、searchButtonは、button2の8px下(デフォルト値)に配置
  • searchTextFidleとsearchButtonの間隔は、水平方向8px(デフォルト値)固定
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-[searchTextField]-[searchButton(100)]-|"
                                                      options:0
                                                      metrics:nil
                                                      views:viewsDictionary];
[self.view addConstraints:constraints];

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[button2]-[searchTextField]"
                                                      options:0
                                                      metrics:nil
                                                        views:viewsDictionary];
[self.view addConstraints:constraints];

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[button2]-[searchButton(30)]"
                                                      options:0
                                                      metrics:nil
                                                        views:viewsDictionary];

だいたいは今までと同じです。1点違うのはsearchButtonの幅と高さに固定値を指定していることです。

  • searchButton(100)で100px固定
  • searchButton(30)で高さ30px固定。先頭にV:が付いているので垂直方向、つまり高さという認識になる

ざっくりと説明しました。他にもいろいろConstraintを設定する方法があるのですが割愛します。理由はこの時点でVisual Format LanguageでのAuto Layoutはありえないと自分は判断したためです・・・

この件は次の記事で説明したいと思います。