コードによる制約の追加

趣味でiOSアプリ開発をかじっていた自分が、改めてiOS開発を勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。いつか書き直します。

## 参考文献

この記事は以下の書籍の情報を参考にして執筆しました。5章を読んでの内容になります。

- [よくわかるAuto Layout iOSレスポンシブデザインをマスター](https://www.amazon.co.jp/%E3%82%88%E3%81%8F%E3%82%8F%E3%81%8B%E3%82%8BAuto-Layout-iOS%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B7%E3%83%96%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%92%E3%83%9E%E3%82%B9%E3%82%BF%E3%83%BC-%E5%B7%9D%E9%82%89-%E9%9B%84%E4%BB%8B/dp/4865940324/)

#コードによる制約の追加
３つの方法がある。
・NSLayoutConstraintのイニシャライザ
・VisualFormatLanguage
・NSLayoutAnchor(iOS9~)

##NSLayoutConstraintのイニシャライザ
メリット : 最も基本的な方法なのでわかりやすい。
デメリット : 制約１つを定義するのに１つイニシャライザを呼び出さないといけない。

```swift:ViewContoroller
    let view1 = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 250))
    view1.backgroundColor = .red
    view1.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(view1)

    /*constraint1 : viewとview1のcenterXを等しくする
     constraint2 : viewとview1のcenterYを等しくする
     constraint3 : view1の幅を設定
     constraint4 : view1の高さを設定*/

    // 制約を設定。幅と高さも必要。
    let constraint1 = NSLayoutConstraint(item: view1, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0.0)
    let constraint2 = NSLayoutConstraint(item: view1, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0.0)
    let constraint3 = NSLayoutConstraint(item: view1, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 100)
    let constraint4 = NSLayoutConstraint(item: view1, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 100)
    view.addConstraints([constraint1, constraint2, constraint3, constraint4])
```
デバッグ画面で制約を確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542614/f6aab63e-90bc-1ef9-a448-455b91aaff93.png)



|引数名|渡す値|
|-------|------|
item|制約を追加する部品
attribute|制約を追加する要素
relatedBy|追加する部品と基準となる部品の関係性
toItem|制約の基準となる部品
attribute|制約の基準となる要素(toItemの要素)
multiplier|制約の割合の数値
constant|制約で追加する数値

relatedByがequalとして式で表すと

```
item.attribute = toItem.attribute * multiplier + constant
```


##VisualFormatLanguage
VSLと略されたり、視覚的書式言語と呼ばれることもある。
NSLayoutConstraintクラスのconstraintsメソッドを使う。戻り値は配列で帰ってくる。
メリット : １つの処理で複数の制約を作成することができる。
デメリット : 制約を決める部分が文字列なので実行時エラーが出ない。

```swift:ViewContoroller
  let view1 = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 250))
  view1.backgroundColor = .red
  view1.translatesAutoresizingMaskIntoConstraints = false
  view.addSubview(view1)

  /*constraint1 : 水平方向の制約。親viewから100ptスペースを空けて幅を指定(metricsで値を指定したパターン)
   constraint2 : 垂直方向の制約。親viewから200ptスペースを空けて高さを指定*/

  let constraints1 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-100-[view1(width)]", options: .alignAllCenterX, metrics: ["width" : 200], views: ["view1" : view1])
  let constraints2 = NSLayoutConstraint.constraints(withVisualFormat: "V:|-200-[view1(200)]", options: .alignAllCenterY, metrics: nil, views: ["view1" : view1])

  // 2つの配列を１つの配列にするためにflatmapを使っている。
  view.addConstraints([constraints1, constraints2].flatMap{ $0 })
```
デバッグ画面で制約を確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542614/6933ae6d-81ff-31dc-ed64-3793724f3edd.png)


###withVisualFormat
VHLフォーマットで制約を記述する

####レイアウトの方向

|記法|意味|
|------|----|
V:|垂直方向の制約を追加
H:|水平方向の制約を追加

####対象オブジェクト

|記法|意味|
|------|----|
[view]|viewオブジェクト
｜|親ビュー

####オブジェクト間のスペース

|記法|意味|
|------|----|
｜-20-[view]|親とviewの距離20pt
｜-(-20)-[view]|親とviewの距離-20pt
｜-[view]|親とviewの距離デフォルト(8pt)
｜[view]|親とviewの距離0pt

####オブジェクトのサイズ
|記法|意味|
|------|----|
H:[view(100)]|viewの幅は100pt
H:[view1(==view2)]view1の幅はview2に等しい

####関係性 - Relationship

|記法|意味|
|------|----|
[view1]-(>=50)-[view2]|view1とview2の距離は50pt以上
H:[view(>= 50, <=100)]|viewの幅は50pt以上、100pt以下 
####優先度
|記法|意味|
|------|----|
H:[view(100@750)]|viewの幅は優先度750で100pt

###options
複数のオブジェクトに対して揃え方を指定する。

例えば、V:[view1]-[view2]のように垂直方向の制約を定義する際は下記のオプションを選択する。

|値|意味|
|------|----|
.alignAllLeft|全てのビューを左寄せ
.alignAllRight|全てのビューを右寄せ
.alignAllLeading|全てのビューをLeading寄せ
.alignAllTrailing|全てのビューをTrailing寄せ
.alignAllCenterX|全てのビューをx方向中央に揃える

水平方向の場合

|値|意味|
|------|----|
.alignAllTop|全てのビューを上部寄せ
.alignAllBottom|全てのビューを下部寄せ
.alignAllCenterY|全てのビューをy方向中央に揃える
.alignAllBaseline|全てのビューをベースラインに揃える

またVFlで記述したレイアウトを左右どちらから並べるか指定するオプションもある。
基本はLeadingからTrailing方向に並べられる。

|値|意味|
|------|----|
.directionLeadingtoTrailing|LeadingからTrailing方向に並べる
.directionLeftToRight|左から右に並べる。
.directionRightToLeft|右から左に並べる。


###metrics
変数を代入するのに使う。

```swift:例
    let constraints1 = NSLayoutConstraint.constraints(withVisualFormat: "H:[view1(width)]", options: .directionRightToLeft, metrics: ["width" : 200], views: ["view1" : view1])
```


##NSLayoutAnchor
メリット : 処理が読みやすい。比較的コードを書く量が少ない。
デメリット : iOS9以降からなのでiOS9以前では使えない。

```swift:ViewContoroller
    let view1 = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 250))
    view1.backgroundColor = .red
    view1.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(view1)
    
    view1.topAnchor.constraint(equalTo: view.topAnchor, constant: 200).isActive = true
    view1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100).isActive = true
    view1.widthAnchor.constraint(equalToConstant: 200).isActive = true
    view1.heightAnchor.constraint(equalToConstant: 200).isActive = true
```

デバッグ画面で制約を確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542614/b97daa9a-ade7-e183-11fa-926efa8c81e2.png)


##制約を編集する
IBOutletで制約を紐づけることができ、コードでプロパティを変更できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542614/21e6d28d-beed-4b9b-dfbe-8d74884a1d38.png)

```swift
  @IBOutlet weak var blueViewTopConstraint: NSLayoutConstraint!
  @IBAction func tapButton(_ sender: Any) {
    blueViewTopConstraint.constant += 50
  }
```

##空間を定義する - UILayoutGuide(iOS9.0~)
空間を定義して、その空間をもとに制約をつけることができる。
空のUIViewを用いるよりも、レンダリングコストが発生しない。余計なビューの階層が増えないというメリットがある。

```swift
class ViewController3: UIViewController {
  
  
  override func viewDidLoad() {
    super.viewDidLoad()

    let view1 = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 250))
    view1.backgroundColor = .red
    view1.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(view1)

    // 空間を定義する
    let space = makeLayoutSpace()

    // spaceを基準に制約をつけることができる
    view1.topAnchor.constraint(equalTo: space.bottomAnchor, constant: 10).isActive = true
    view1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100).isActive = true
    view1.widthAnchor.constraint(equalToConstant: 200).isActive = true
    view1.heightAnchor.constraint(equalToConstant: 200).isActive = true
  }

  func makeLayoutSpace() -> UILayoutGuide{
    let activeFlag = true
    view.addLayoutGuide(space)
    space.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = activeFlag
    space.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = activeFlag
    space.widthAnchor.constraint(equalToConstant: 200).isActive = activeFlag
    space.heightAnchor.constraint(equalToConstant: 200).isActive = activeFlag

    return space
  }
```

デバッグ画面で確認すると定義した空間からの制約は見えず、viewの階層にも空間は存在しないが、確かに定義した空間からの制約をもとにviewを表示できている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542614/2f9b2627-35ba-52c3-a3c3-da87e874c4f3.png)