AutoLayoutの制約の有効／無効化方法4つを比較する

コードからAutoLayoutの制約の有効化／無効化を切り替える方法は以下の4つがあります。

- `UIView`の`add(remove)Constraint(_:)`
- `UIView`の`add(remove)Constraints(_:)`
- `NSLayoutConstraint`の`isActive`
- `NSLayoutConstraint`の`(de)activate(_:)`

この4つの方法の違いを解説します。

## `UIView`の`add(remove)Constraint(_:)`

この方法は**非推奨**です！

`UIView`のインスタンスメソッドの`addConstraint(_:)`と`removeConstraint(_:)`を使う方法です。

以下のように使います。

```swift
let widthConstraint = aView.widthAnchor.constraint(equalToConstant: 100)
aView.addConstraint(widthConstraint)
```

簡単ですね。

では、以下のようなViewの階層を考えてみます。

![image1.png](https://qiita-image-store.s3.amazonaws.com/0/130238/69401645-d286-5ac3-4e14-a9e580d33859.png)

viewBとviewCはviewAのsubviewです。
ここで、「viewBとviewCの横幅が等しい」という制約を加えたいとしましょう。以下にコードを示します。

```swift
let widthEqualConstraint = viewB.widthAnchor.constraint(equalTo: viewC.widthAnchor)
viewA.addConstraint(widthEqualConstraint)
```

上記のコードで注意するべきことは、viewAに対して`addConstraint(_:)`を呼んでいることです。
ここで間違えて、viewBやviewCに対して呼んでいたら、クラッシュするか制約が働かなくなります。つまり、

*`addConstraint(_:)`は、制約に関連するviewに共通の親viewのうち最も階層の近いものに対して呼ばないといけません。そうしないと、クラッシュするか制約が効かなくなってしまいます。*

先ほどの例だと、viewBとviewCの共通の親であるviewA対して呼ばなければいけません。ここが難しいところです。幸いなことに回避方法はあるので、後ほど紹介します。

#### `removeConstraint(_:)`について

`removeConstraint(_:)`は、制約を解除します。それ以外は`addConstraint(_:)`と同じです。

## `UIView`の`add(remove)Constraints(_:)`

このメソッドは`NSLayoutConstraint`の配列を引数にとります。制約を一度に有効化／無効化することができますが、`add(remove)Constraint(_:)`と同じく適切なviewに対してメソッドを呼ばなければならないという問題は残っています。したがって、このメソッドはおすすめできません。

## NSLayoutConstraintの`isActive`

`NSLayoutConstraint`のプロパティの`isActive`を使います。例を示します。

```swift
let widthConstraint = aView.widthAnchor.constraint(equalToConstant: 100)
widthConstraint.isActive = true
// aView.widthAnchor.constraint(equalToConstant: 100).isActive = ture でもOK
```

ここで再び以下の画像を考えてみます。viewBとviewCはviewAのsubviewです。

![image1.png](https://qiita-image-store.s3.amazonaws.com/0/130238/69401645-d286-5ac3-4e14-a9e580d33859.png)

`isActive`を使うとviewBとviewCの横幅が等しい制約を有効化する方法は以下のようになります。

```swift
let widthEqualConstraint = viewB.widthAnchor.constraint(equalTo: viewC.widthAnchor)
widthEqualConstraint.isActive = true
viewA.constraints.contains(widthEqualConstraint) // => true
```

3行目に注目してください。`widthEqualConstraint`がviewAに所属していることを示しています。このように、`isActive`を使うとその制約がどのviewに所属するべきかを自動的に解決してくれます。制約を解除したいときは`isActive = false`としてやればOKです。非常に便利ですね。

## `NSLayoutConstraint`の`(de)activate(_:)`

`NSLayoutConstraint`のクラスメソッドです。`NSLayoutConstraint`の配列を引数にとり、制約の有効化／無効化を一度に設定します。
このメソッドも、与えられた制約がどのviewに所属するかを自動的に解決してくれます。

## 結局どれを使えばいいの？

わかりやすく表にしました。

| 方法 | 所属するviewの自動解決 |
|:-----------------|------------------|
| `add(remove)Constraint(_:)`| ❌ |
| `add(remove)Constraints(_:)` | ❌ |
| `isActive` | ⭕️ |
| `(de)activate(_:)` | ⭕️ |

上の二つは所属するviewの自動解決をしてくれないので使うのをやめましょう。もし間違ったviewに対してメソッドを呼んでしまった時のリスクが大きすぎます。
下の二つなら正直に言ってどちらでも良いのですが、`NSLayoutConstraint`の`(de)activate(_:)`の方がパフォーマンスの点で有利です。これも表にしてみます。

| 方法 | パフォーマンス |
|:-----------------|------------------|
| `isActive` | ⭕️ |
| `(de)activate(_:)` | 💯 |

[ドキュメント](https://developer.apple.com/documentation/uikit/nslayoutconstraint/1526955-activate)によると、

> Typically, using this method is more efficient than activating each constraint individually.

と書いてあるので、`NSLayoutConstraint`の`(de)activate(_:)`の方がパフォーマンスが有利だと考えて良いです。
その理由は、WWDC 2014の[Mysteries of Auto Layout, Part 2](https://developer.apple.com/videos/play/wwdc2015/219/)の5:20 ~ 5:35で言及されています。一度に複数の制約を有効化／無効化することでレイアウトのライフサイクルが無駄に回らないのが理由のようです。

### 結論

有効化／無効化したい制約が一つだけなら`isActive`あるいは`NSLayoutConstraints`の`(de)activate(_:)`のどちらを使っても良いです。ただし、複数の制約がある場合は`NSLayoutConstraints`の`(de)activate(_:)`がパフォーマンスの点で有利です。