逆引きSnapKit - AutoLayoutをコードからサクッと設定しよう

# はじめに

SwiftUIの登場でAutoLayoutの出番は少なくなったものの、業務ではまだまだAutoLayoutを使用する機会も多いのではないでしょうか？

AutoLayoutと言えばStoryboardを思い浮かべる人も多いと思います。しかし、もっさりした動作のStoryboard（Interface builder）をわざわざ開かなくとも、AutoLayoutを設定することができます。[SnapKit](https://github.com/SnapKit/SnapKit)ならめちゃめちゃ簡単に！

## SnapKitを使ったサンプルコード

view1とview2を同じ大きさにして左揃えで縦に並べる例

SnapKitなし

```swift
guard let superview = view1.superview else {
    return
}
view1.translatesAutoresizingMaskIntoConstraints = false
view2.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    view1.widthAnchor.constraint(equalToConstant: 200),
    view1.heightAnchor.constraint(equalToConstant: 50),
    view1.topAnchor.constraint(equalTo: superview.topAnchor, constant: 50),
    view1.leftAnchor.constraint(equalTo: superview.leftAnchor, constant: 50),
    view2.widthAnchor.constraint(equalTo: view1.widthAnchor),
    view2.heightAnchor.constraint(equalTo: view1.heightAnchor),
    view2.leftAnchor.constraint(equalTo: view1.leftAnchor),
    view2.topAnchor.constraint(equalTo: view1.bottomAnchor, constant: 16)
])
```

SnapKitあり

```swift
view1.snp.makeConstraints { make in
  make.size.equalTo(CGSize(width: 200, height: 50))
  make.top.left.equalTo(50)
}
view2.snp.makeConstraints { make in
  make.left.size.equalTo(view2)
  make.top.equalTo(view2.snp.bottom).offset(16)
}
```

いかがでしょうか？SnapKitを使うことで書き味や可読性が、グッと向上していることがおわかりになると思います。ちなみに2020.07現在、[SnapKit](https://github.com/SnapKit/SnapKit)はGitHubで1万6千スターを獲得しています。（Storyboard嫌いの多さにビックリ！）

本記事では、SnapKitの使い方を逆引きリファレンス形式で紹介していきます。

## 動作確認環境

- Xcode 11.6
- Swift 5
- SnapKit 5.0.1

# まずは基本から

### makeConstraintsメソッドのクロージャ内で制約を設定する
```swift
// SnapKitをインポートすると、UIViewにsnpというプロパティが生える
import SnapKit
  
// snpプロパティのmakeConstraintsメソッドの引数のクロージャ内で、制約を設定する
view.snp.makeConstraints { make in
    // view.top = otherView.top
    make.top.equalTo(otherView.snp.top)
    // view.left = otherView.right + 8
    make.left.equalTo(otherView.snp.right).offset(8)
}
```
これ以降、make変数が出てきた場合は、makeConstraintsのクロージャ内での記述です。

### 同名のプロパティに対する制約を設定するときは、引数にUIView/UILayoutGuideを渡すことができる

```swift
// make.top.equalTo(otherView.snp.top)と同じ
make.top.equalTo(otherView)
```

### 複数のプロパティをチェーンで設定できる

```swift
// make.top.equalTo(otherView); make.bottom.equalTo(otherView)と同じ
make.top.bottom.equalTo(otherView)
```

### 親viewに対する制約のときは専用のメソッドが用意されている

```swift
make.top.equalToSuperview()
make.top.lessThanOrEqualToSuperview()
make.top.greaterThanOrEqualToSuperview()
```    

### 幅と高さには定数を指定できる

```swift
// width = 100
make.width.equalTo(100)
// height <= 200
make.height.lessThanOrEqualTo(200)
// width >= 400 && height >= 400
make.size.greaterThanOrEqualTo(CGSize(width: 400, height: 400))
```

### 幅と高さ以外に定数を指定すると、親ビューに対する制約になる

```swift
// left <= superview.left + 10
// make.left.lessThanOrEqualToSuperview().offset(10)と同じ
make.left.lessThanOrEqualTo(10)
```

# 逆引きリファレンス

### 制約の優先度を設定する
```swift
make.top.equalTo(otherView).priority(600)
make.top.equalTo(otherView).priority(.low)      // = 250
make.top.equalTo(otherView).priority(.medium)   // = 500（OSXの場合は501）
make.top.equalTo(otherView).priority(.high)     // = 750
make.top.equalTo(otherView).priority(.required) // = 1000
```

### 制約にラベルを付ける

AutoLayoutの警告ログやDebug View Hierarchy画面に表示されます

```swift
make.top.equalTo(otherView).labeled("viewTopConstraintToOtherView")
```

### 上下左右（top/bottom/left/right）の制約をまとめて設定する

```swift
// make.top.bottom.left.right.equalTo(otherView)と同じ
make.edges.equalTo(otherView)
    
// Insetsを指定する場合
// ConstraintInsetsはUIEdgeInsetsのaliasなので、UIEdgeInsetsも使用可
make.edges.equalTo(otherView).inset(ConstraintInsets(top: 8, left: 16, bottom: 8, right: 16))
    
// 親ビューに対するinsetsなら直接渡せる
// make.edges.equalToSuperView().inset(...)と同じ
make.edges.equalTo(UIEdgeInsets(top: 32, left: 12, bottom: 32, right: 12))
```

### 幅と高さに相対的な制約を設定する

```swift
// width = otherView.width * 2 + 50
make.width.equalTo(otherView).multipliedBy(2).offset(50)
// height = superview.height / 10
make.height.equalToSuperview().dividedBy(10)
```

### サイズ（幅と高さ）の制約をまとめて設定する
```swift
make.size.equalTo(CGSize(width: 100, height: 100)) 
    
// width = otherView.width - 50, height = otherView.height - 50
make.size.equalTo(otherView).offset(-50)
```

### 中央の制約を設定する

```swift
// centerX = otherView.centerX + 10
make.centerX.equalTo(otherView).offset(10)
make.centerY.equalTo(otherView).offset(10)
    
// 上の2つと同じ
make.center.equalTo(otherView).offset(10)
```

### SafeAreaに対する制約を設定する
```swift
guard let guide = view.rootSafeAreaLayoutGuide else {
    return
}
view.snp.makeConstraints { make in
    make.edges.equalTo(guide)
}
    
...
extension UIView {
    var rootSafeAreaLayoutGuide: UILayoutGuide? {
        var rootView: UIView? = self
        while rootView?.superview != nil {
            rootView = rootView?.superview
        }
        return rootView?.safeAreaLayoutGuide
    }
}
```

### Widthを割合で指定しつつ、最大値を設定する

```swift
// 1. 親ビューの幅の半分に設定する
make.width.equalToSuperview().dividedBy(2).priority(.high)
// 2. 1.の制約より優先度を上げて最大値を指定する
make.width.lessThanOrEqualTo(100).priority(.required)
```

### アスペクト比を保ったまま、親ビューの中で最大限大きく表示する

```swift
// width : height = 2 : 1のアスペクト比に設定する
make.width.equalTo(inner.snp.height).multipliedBy(2).priority(.required)
// 親ビューのサイズを超えないように設定する
make.size.lessThanOrEqualToSuperview().priority(.required)
// 上記より優先度を下げて、幅と高さを親ビューと同じに設定する
make.width.equalToSuperview().priority(.high)
make.height.equalToSuperview().priority(.high)
    
// 中央に表示する
make.center.equalToSuperview()
```

### 制約の参照を保持する

```swift
let topConstraint: Constraint
view.snp.makeConstraints { make in
    // constraintプロパティを呼び出す。この制約は自動的にactivateされる
    topConstraint = make.top.equalToSuperview().offset(8).constraint
}
    
// 制約をdeactivate
topConstraint.deactivate()
    
// 制約をactivate
topConstraint.activate()   
    
// Offsetを更新
topConstraint.update(offset: 16)
```
    
### アニメーションに制約を使う

```swift
let heightConstraint: Constraint
    
view.snp.makeConstraints { make in
    heightConstraint = make.height.equalTo(100).offset(0).constraint
}
        
UIView.animate(withDuration: 1, animations: {
    self.heightConstraint?.update(offset: 200)
    self.layoutIfNeeded()
})
```

### UIView#updateConstraints, UIViewController#updateViewConstraints内で使用する

```swift
// updateConstraintsメソッドは制約のconstantだけを更新する
view.snp.updateConstraints { make in
    make.height.equalTo(self.height)
}
```

### 既存の制約を破棄する

```swift
view.snp.removeConstraints()
```
                    
### 既存の制約を破棄して、新しい制約を設定する

```swift
@objc func changeHeight(sender: UIButton) {
    // remakeConstraintsメソッドは既存の制約を破棄するとともに、新しい制約を設定する
    snp.remakeConstraints { make in
        make.height.equalTo(sender.tag)
        make.top.left.equalToSuperview()
        make.width.equalTo(100)
    }
}
```
# 参考

[SnapKit Doc](http://snapkit.io/docs/)