Visual Format Languageを使う【Swift3.0】

パッと見、訳の分からない文字列のせいで取っ付きにくい印象がありますが慣れてしまえば実はそうでもないんです。制約付けのコツは、ストーリーボードでの制約にも言えることですが自分が部品になったつもりになって「自分の居場所は何と何が決まれば確定するのだろうか...」と考えることかなと思います。

### お約束
- `translatesAutoresizingMaskIntoConstraints = false`を忘れずに！
- 制約を付けるのは`addSubviewの後`です！

# 実践／サンプル

## 1.縦横サイズの指定
横サイズの指定
**H:[{object}(=={value})]**

縦サイズの指定
**V:[{object}(=={value})]**

*※{object}はオブジェクトのディクショナリキーを指定します。以下同様*

試してみましょう。次のコードはボタンをひとつ作成し、縦横サイズの指定をしています。横を示す「H:」は省略可能です。制約は**addConstraints**で設定します。メソッド名は複数形です。viewsパラメタにはオブジェクトのディクショナリを渡します。

```swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ボタン
        let button = UIButton(type: UIButtonType.system)
        button.backgroundColor = UIColor.yellow
        button.setTitle("button", for: .normal)
        view.addSubview(button)
        
        // オブジェクトをディクショナリに格納
        let objects = ["button":button]
        
        objects.forEach { $1.translatesAutoresizingMaskIntoConstraints = false }
        
        button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==140)]", options: .alignAllTop, metrics: nil, views: objects))
        button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[button(==50)]", options: .alignAllTop, metrics: nil, views: objects))
 
        
    }
}
```
実行結果
<img width="379" alt="0.png" src="https://qiita-image-store.s3.amazonaws.com/0/151314/2480a101-4135-d365-fe99-2c36ed1a7351.png">

## 2.オフセットで位置を指定
左マージンを指定
**H:|-{value}-[{object}]**

上マージンを指定
**V:|-{value}-[{object}]**

右マージンを指定
**H:[{object}]-{value}-|**

下マージンを指定
**V:[{object}]-{value}-|**

左右マージンを指定
**H:|-{value}-[{object}]-{value}-|**

上下マージンを指定
**V:|-{value}-[{object}]-{value}-|**


「|」が隣接するオブジェクトの境界線、「-」が距離を示しているわけですね。

コードに以下を加えてみます。ボタンが外側(=View)から横50px、上から100pxの位置に表示されます。addConstraintsメソッドをコールしているのが相対関係にある外側のオブジェクト(view)であることに注意してください。

```swift
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[button]", options: .alignAllTop, metrics: nil, views: objects))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-100-[button]", options: .alignAllTop, metrics: nil, views: objects))
```
<img width="378" alt="1.png" src="https://qiita-image-store.s3.amazonaws.com/0/151314/554e5b3b-1446-ee03-0901-4430efd01099.png">

ちなみに左右のマージンを50pxにするには ***H:|-50-[button]*** を ***H:|-50-[button]-50-|*** にします。

```swift
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[button]-50-|", options: .alignAllTop, metrics: nil, views: objects))
```

<img width="377" alt="2.png" src="https://qiita-image-store.s3.amazonaws.com/0/151314/0b8a35d2-7220-0ae3-d83f-da4cde6aa9cc.png">

## 3.オブジェクト間での制約
オブジェクト2の横サイズをオブジェクト1の横サイズと同じにする
**H:[{object1}(=={object2})]**

オブジェクト2の縦サイズをオブジェクト1の縦サイズと同じにする
**V:[{object1}(=={object2})]**

オブジェクト2をオブジェクト1の右横に配置する
**H:[{object1}]-{value}-[{object2}]**

オブジェクト2をオブジェクト1の下に配置する
**V:[{object1}]-{value}-[{object2}]**


テキストフィールドをひとつ追加して、ボタンと同じサイズ、ボタンの横にマージン10pxを指定して表示させてみます。コードにテキストフィールドの生成処理を追加し、オブジェクト配列に追加します。

```swift
        // テキストフィールド
        let textField = UITextField()
        textField.borderStyle = .roundedRect
        textField.text = "textfield"
        view.addSubview(textField)
        
        
        // オブジェクトをディクショナリに格納
        let objects = ["button":button,"textField":textField]

```

```swift
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[button(==textField)]", options: .alignAllBottom, metrics: nil, views: objects))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==textField)]", options: .alignAllBottom, metrics: nil, views: objects))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button]-10-[textField]", options: .alignAllBottom, metrics: nil, views: objects))
```
<img width="378" alt="3.png" src="https://qiita-image-store.s3.amazonaws.com/0/151314/242b88f4-a1e2-8da2-89d0-0d3871e45193.png">

現在のコードから ***H:[button]-10-[textField]*** を ***V:[button]-10-[textField]*** に変更するだけでテキストフィールドがボタンの下に表示されそうですが実はそれだけではダメです。上下の制約に変更した場合、optionsのパラメタ(揃えの基準)も適したものを設定しないといけません。「.alignAllLeft」か「.alignAllRight」にする必要があります。

## 4.各要素を一直線に並べたレイアウト
オブジェクト1とのマージンを確保しながらオブジェクト2の横サイズを可変にする
**|-[{object1}]-[{object2}(>={value})]-|**

ボタンの横マージンとテキストフィールドの横幅と位置の制約をコメントアウトして、横並びにする制約を加えます。数値指定のない「-」のみの場合は8ピクセルのマージンが付きます。この例ではテキストフィールドが20px以下にならない範囲で可変となります。

```swift
    //view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[button]", options: .alignAllTop, metrics: nil, views: objects))
    //view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==textField)]", options: .alignAllBottom, metrics: nil, views: objects))
    //view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button]-10-[textField]", options: .alignAllBottom, metrics: nil, views: objects))
    view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|-[button]-[textField(>=20)]-|", options: .alignAllBottom, metrics: nil, views: objects))
```
<img width="377" alt="4.png" src="https://qiita-image-store.s3.amazonaws.com/0/151314/a054f995-747b-55fa-e7b3-fea0fff1451d.png">

次のコードを加えてボタンの横幅を変えてみます。

```swift
    button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==50)]", options: .alignAllTop, metrics: nil, views: objects))
```
<img width="380" alt="5.png" src="https://qiita-image-store.s3.amazonaws.com/0/151314/3374c910-f2c6-d65d-ca17-a32fd677c2da.png">

テキストフィールドの横幅も追従します。

## テストコード全体

```swift

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        
        // ボタン
        let button = UIButton(type: UIButtonType.system)
        button.backgroundColor = UIColor.yellow
        button.setTitle("button", for: .normal)
        view.addSubview(button)
        
        // テキストフィールド
        let textField = UITextField()
        textField.borderStyle = .roundedRect
        textField.text = "textfield"
        view.addSubview(textField)
        
        
        // オブジェクトをディクショナリに格納
        let objects = ["button":button,"textField":textField]
        
        objects.forEach { $1.translatesAutoresizingMaskIntoConstraints = false }
        
        // オブジェクト配列
        let objects = ["button":button,"textField":textField]
        
        objects.forEach { $1.translatesAutoresizingMaskIntoConstraints = false }
        
        button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==140)]", options: .alignAllTop, metrics: nil, views: objects))
        button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[button(==50)]", options: .alignAllTop, metrics: nil, views: objects))
        //view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[button]", options: .alignAllTop, metrics: nil, views: objects))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-100-[button]", options: .alignAllTop, metrics: nil, views: objects))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[button(==textField)]", options: .alignAllBottom, metrics: nil, views: objects))
        //view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==textField)]", options: .alignAllBottom, metrics: nil, views: objects))
        //view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button]-10-[textField]", options: .alignAllBottom, metrics: nil, views: objects))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|-[button]-10-[textField(>=20)]-|", options: .alignAllBottom, metrics: nil, views: objects))
        button.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[button(==50)]", options: .alignAllTop, metrics: nil, views: objects))
 
        
    }
}
```