Auto Layoutをコードで書いて学ぶ（Swift 2.2）

この記事はSwift 2.2で記述したものです。Swift 3.0におけるNSLayoutAnchorの使用例をまとめたものはこちら↓です。
http://qiita.com/shindooo/items/36d2e8bf9d8ba3fa4ed5

##はじめに
iOSアプリ開発において、Auto Layoutはつまづきやすいところです。
また、GUI（IB）ではAuto Layoutを設定しているけれど、コードでビューを追加したときはAuto Layoutを使っていないということもあるようです。

ここではコードでAuto Layoutを設定することを通して、Auto Layoutの考え方をおさらいします。

##Auto Layoutの考え方
Auto Layoutでは「制約（Constraint）」を設定することで、ビューの位置やサイズの決定します。

制約は、位置・サイズについてビュー同士の相対的な関係を定義したものです。
相対的な関係というのは

```
緑のビューの幅は、青のビューの幅を1.5倍して30pt加えた大きさである
```

というようなことです。

このことを数式で表すと次のようになります。

```
greenView.width = 1.5 × blueView.width + 30
```

さらに一般的に書くと

```
y = ax + b
```

簡単な一次関数ですね。この式は非常に重要であり、制約の本質を表しています。
制約を定義するということは、この式の各要素を指定するということになります。


##制約を定義する
コードで制約（NSLayoutConstraint）を生成する方法には次の３つがあります。

1. NSLayoutAnchor（iOS 9 〜）を使う
1. NSLayoutConstraintのイニシャライザで生成する
1. Visual Format Languageを使う

iOS 9以降であれば、NSLayoutAnchorを使うのがオススメです。
そこで、ここではNSLayoutAnchorを使う方法を中心に説明し、残りのふたつの方法については簡単な説明だけにとどめます。

###事前準備
次のようにコードでビューを追加します。

```Swift
let blueView = UIView()
blueView.backgroundColor = UIColor.greenColor()
blueView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(blueView)
```

ポイントは、`translatesAutoresizingMaskIntoConstraints`にfalseを設定しているところです。
Auto Layout登場以前はAutoresizingMaskという仕組みで、ビューの動的なサイズ変更を実現していました。
このtranslatesAutoresizingMaskIntoConstraintsは、AutoresizingMaskの設定値をAuto Layoutの制約に変換するかどうかを決めるものです。
これをtrueにすると、大抵自分で追加する制約とコンフリクトしてしまうので、特に理由がなければ上記のようにfalseにしておくのがよいでしょう。


###NSLayoutAnchorを使う
では早速、NSLayoutAnchorを使った例を見てみましょう。

```Swift
let leadingConstraint = blueView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor,
                                                                       constant: 30.0)
```

上記は

```
青のビューの左端は、親ビューの左端から30ptの位置である
```

という制約です。


これを`y = ax + b`の式に当てはめると、

+ `y` → blueView.leadingAnchor
+ `=` → constraintEqualToAnchor
+ `a` → （位置の制約ではaは指定できないので1）
+ `x` → self.view.leadingAnchor
+ `+ b` → constant: 30.0

ということになります。

ここまではただ制約を生成しただけです。
この制約を有効にするには以下のように

```Swift
leadingConstraint.active = true
```

とします。
これは、つぎのように1行で書くこともできます。

```Swift
blueView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor, constant: 30.0).active = true
```

このように、NSLayoutAnchorを使えばシンプルに制約を設定することができます。

それではNSLayoutAnchorのまとめとして、簡単なサンプルを載せておきます。

```ViewController.swift
override func viewDidLoad() {
    super.viewDidLoad()
    
    let blueView = UIView()
    blueView.backgroundColor = UIColor.blueColor()
    blueView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(blueView)
    
    let greenView = UIView()
    greenView.backgroundColor = UIColor.greenColor()
    greenView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(greenView)
    
    // 青のビューの左端は、親ビューの左端から30ptの位置
    blueView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor, constant: 30.0).active = true
    // 青のビューの縦方向の中心は、親ビューの縦方向の中心と同じ
    blueView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor).active = true
    // 青のビューの幅は、親ビューの幅の1/4
    blueView.widthAnchor.constraintEqualToAnchor(self.view.widthAnchor, multiplier: 0.25).active = true
    // 青のビューの高さは30pt
    blueView.heightAnchor.constraintEqualToConstant(30.0).active = true
    
    // 緑のビューの左端は、青のビューの右端から20ptの位置
    greenView.leadingAnchor.constraintEqualToAnchor(blueView.trailingAnchor, constant: 20.0).active = true
    // 緑のビューの上端は、青のビューの上端と同じ位置
    greenView.topAnchor.constraintEqualToAnchor(blueView.topAnchor).active = true
    // 緑のビューの幅は、青のビューの幅を1.5倍して30pt加えた大きさ
    greenView.widthAnchor.constraintEqualToAnchor(blueView.widthAnchor, multiplier: 1.5, constant: 30.0).active = true
    // 緑のビューの高さは、青のビューの高さと同じ大きさ
    greenView.heightAnchor.constraintEqualToAnchor(blueView.heightAnchor).active = true
}
```

![iphone_iPad2.png](https://qiita-image-store.s3.amazonaws.com/0/50559/b2f3d80e-48da-3915-e79c-5a75ff414446.png)

###NSLayoutConstraintのイニシャライザで制約を生成
NSLayoutConstraintのイニシャライザで直接制約を生成することもできます。
iOS 8以前の最も基本的な制約定義の方法であるといえます。

以下のように、`y = ax + b`の式の各要素を全て引数で指定する形になります。

```Swift
// 緑のビューの幅は、青のビューの幅を1.5倍して30pt加えた大きさ
let widthConstraint = NSLayoutConstraint(item: greenView, attribute: NSLayoutAttribute.Width,
                                         relatedBy: NSLayoutRelation.Equal,
                                         toItem: blueView, attribute: NSLayoutAttribute.Width,
                                         multiplier: 1.5, constant: 30.0)
                                         
widthConstraint.active = true
```


###Visual Format Language
Visual Format Language（VFL）とは、アスキーアートのように文字列で図を表現したものです。
これを使って制約を定義した例を以下に記載します。`"H:[blueView]-20-[greenView]"`の部分がVFLです。

```Swift
// 緑のビューの左端は、青のビューの右端から20ptの位置
let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:[blueView]-20-[greenView]",
                                                                                   options: .AlignAllCenterY,
                                                                                   metrics: nil,
                                                                                   views: ["blueView": blueView, "greenView": greenView])
                                                                                   
NSLayoutConstraint.activateConstraints(horizontalConstraints)
```

制約を文字列の見た目で表現するので、直感的に理解しやすいです。
そのためか、制約がコンフリクトしたときのエラーメッセージにもVFLが使われています。
ただし文字列であるため、実行するまで記述の誤りがわからないというのが大きな欠点です。

書式の詳細はAppleのドキュメントを参照してください。

##おわりに
Auto Layoutを用いたレイアウトは、それ以前の方法にくらべると確かに少し難しいように思います。
それでも、基本的な考え方をしっかりと理解したうえで使用すれば、コンフリクトや意図しないレイアウト崩れをある程度防ぐことができるはずです。

##参考
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/