Debugging your layout ([WWDC2015] Mysteries of Auto Layout, Part 2より)

WWDC2015のセッションである[Mysteries of Auto Layout, Part 2](https://developer.apple.com/videos/play/wwdc2015/219/)から、Auto Layoutのデバッグの部分だけ抜粋してざっくりまとめました。

# レイアウトのデバッグ

## ログを読む

Auto Layoutを使ってUIを作っていると、たまに意図しないレイアウトになることがあります。この時に出るログについて少し見ていきます。

<img width="800" alt="スクリーンショット 2018-08-11 22.32.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/85326863-eb6c-c44d-c9c7-b9ec09b31f70.png">

まず一番下をチェックします。これはエンジンによって壊された制約です。必ずしもこの制約が原因とは限りませんが取り掛かりとしては良いと言えます。

<img width="800" alt="スクリーンショット 2018-08-11 22.26.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/1b68eaa6-5318-4911-e720-7d69e3d86555.png">

まず最初にtranslatesAutoResizingMaskをチェックします。今回の場合、Saturnのaspect比の制約が壊れているので、この周辺の制約を見ていきます。

Saturn自体はsuperviewに対するleadingの制約、trailingの制約、topの制約、labelに対するbottomの制約は問題ありません。なので今度は接しているlabelを詳しく見ていきます。

このlabelには先程のSaturnのbottomとlabelのtopをつなぐ制約と、labelのtopとsuperviewのtopをつなぐ制約がついています。Saturnは100ポイント以上あると思われるので、この制約がまずそうです。

<img width="800" alt="スクリーンショット 2018-08-11 22.57.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/cf3062f1-c321-efb3-e95e-95b189431fef.png">

## identifierを付けてログを読みやすくする

ログを読むことは難しくありませんが、より読みやすくしてみましょう。

<img width="400" alt="スクリーンショット 2018-08-11 23.09.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/3f955766-2c8f-854b-a74b-c65ed4b09808.png">　<img width="400" alt="スクリーンショット 2018-08-11 23.11.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/c66c6f41-2cf6-9b78-4762-927f35e3adf5.png">

右側の方が見やすいと思います。このようにするためにはidentifierを付ける必要があります。明示的に制約を付けている場合は次のように指定します。

```objective_c
labelToTop.identifier = @"labelToTop.identifier";
```

もしビジュアルフォーマットを使っている場合は、ループ処理を利用して配列内の全ての制約に対して同じidentifierを付けます。

```objective_c
for (NSLayoutConstraint *constraint in verticalLayout)
{
    constraint.identifier = @"verticalLayout";
}
```

Interface Builderで設定する場合は以下の箇所から設定します。

<img width="250" alt="スクリーンショット 2018-08-11 23.44.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/ed76b67f-c7b0-be57-97d7-c9a5c78cf518.png">

## ログを理解する

identifierはviewに対しても付けることができます。これにより、ログを解読する労力を減らすことができます。

もしログが大量すぎる場合は1方向だけの制約を出力してくれる`constraintsAffectingLayoutForAxis` (iOS用)、`constraintsAffectingLayoutForOrientation` (OS X用)を使うと良いです。

ブレークポイントを張ってデバッガにconstraintsAffectingLayoutForAxisを入力してSaturnの縦の制約を出力してみます。ここではメモリアドレスではなくidentifierで指定できるようにしています。verticalは1、horizontalは0です（現在Swiftで書く場合は`self.view.constraintsAffectingLayout(for: .vertical)`のような形になります）。

<img width="800" alt="スクリーンショット 2018-08-12 13.42.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/68410c03-a15b-987a-27a7-ec7c178956aa.png">

topの制約は問題なさそうなこと、Saturnの下にも制約があること、labelのtopからsuperviewに伸びている制約があることがわかります。ここから怪しい制約を発見することもできます。

ログを理解するためにすると良いことは以下の通りです。

* 下から見ていく
* 最初に`translatesAutoresizingMaskIntoConstraints`の設定をチェックする
* identifierを設定する
* `constraintsAffectingLayoutForAxis:`を使う

# 制約の曖昧さを解消する

今度は制約が曖昧で、解決方法が複数存在する場合について見ていきます。

## レイアウトが正しく表示されない原因

考えられる理由は例えば次の通りです。

* 制約が足りない
* 優先度がコンフリクトしている

`contentHuggingPriority`(大きくなりにくさ / コンテンツに沿う優先度)と`compressionResistancePriority`(コンテンツの潰れにくさ)の設定によってはコンフリクトしてレイアウトが崩れてしまうことがあります。

## 解決するために役立つツール

以下のツールから手がかりが得られます。

* Interface Builderの赤/黄色のアイコン
* `_autolayoutTrace`
  * IBを使用していないときに便利
  * 曖昧なレイアウトをビューの階層情報とともに出力してくれる
* ビューデバッガー
* `exerciseAmbiguityInLayout`
  * 曖昧だとわかっているviewに対して使う
  * 制約を満たす範囲でランダムに制約を付けてくれる
* `hasAmbiguousLayout`
  * 曖昧なレイアウトかどうかをBoolで返す

# デバッグのポイント

* レイアウトするのに必要な情報は何かを考える
* うまくいかないときはログを使う
  * ビューや制約にidentifierを付けておく
* 定期的にチェックする
  * 単体テストとして入れてしまうなど
* ツールを使う
  * Interface Builderのアイコン
  * ビューデバッガー
  * LLDBのメソッド