崩れたレイアウトの調査方法

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

## 参考文献

この記事は以下の書籍の情報を参考にして執筆しました。

- [よくわかる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/)


##レイアウトが崩れる原因
レイアウトが一意に定まっていない、曖昧なレイアウト。
意図しない制約が反映されてしまう、制約のコンフリクトがある。

##曖昧なレイアウト
####レイアウトが曖昧かどうか調べる
hasAmbiguousLayout()を用いてレイアウトが曖昧かどうか調べることができる。

```swift
view.hasAmbiguousLayout
```

下記のようにviewの制約を設定している途中で実行するとtrueが帰ってくる。
hasAmbiguousLayout()がtrueの状態ではviewは表示されない。

```swift
      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
      print(view1.hasAmbiguousLayout)    // true

      view1.heightAnchor.constraint(equalToConstant: 200).isActive = true
      print(view1.hasAmbiguousLayout)    // false
```

#制約のコンフリクト
制約のコンフリクトはレイアウトエンジンによって自動的に不必要な制約が削除されることによって解決されるが、意図しないレイアウトとなることがあるのでレイアウトが崩れてしまうことがある。

##制約のコンフリクトをデバッグする
例として意図的に制約のコンフリクトを発生させる。

```swift
    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.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 200).isActive = true
    
    view1.widthAnchor.constraint(equalToConstant: 200).isActive = true
    view1.heightAnchor.constraint(equalToConstant: 200).isActive = true
```

####エラーメッセージを読み解いて確認
実行時のエラーメッセージにコンフリクトした制約の概要が記載されている。
VisualFormatLanguage形式で記載されている。

```
[16824:2982179] [LayoutConstraints] Unable to simultaneously satisfy constraints.
	Probably at least one of the constraints in the following list is one you don't want. 
	Try this: 
		(1) look at each constraint and try to figure out which you don't expect; 
		(2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x600003802a30 H:|-(100)-[UIView:0x7fde9f408970]   (active, names: '|':UIView:0x7fde9f40be70 )>",
    "<NSLayoutConstraint:0x600003802a80 H:|-(200)-[UIView:0x7fde9f408970]   (active, names: '|':UIView:0x7fde9f40be70 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600003802a80 H:|-(200)-[UIView:0x7fde9f408970]   (active, names: '|':UIView:0x7fde9f40be70 )>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
```

####ビューデバッガーで確認
ビューデバッガーでの表示。画面左からの距離の制約が２つあることがわかる。
それぞれ[右クリック]→[Print Discripption of ~]で制約を表示させると値を確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542614/6c1c84bc-3990-476f-0d94-e4a9cf180291.png)

```
Printing description of $21:
<NSLayoutConstraint:0x600003802a30 H:|-(100)-[UIView:0x7fde9f408970]   (active, names: '|':UIView:0x7fde9f40be70 )>
Printing description of $22:
<NSLayoutConstraint:0x600003802a80 H:|-(200)-[UIView:0x7fde9f408970]   (active, names: '|':UIView:0x7fde9f40be70 )>
```
##VisualFormatLanguageを読みやすくするツール
https://www.wtfautolayout.com/

####Viewの名称
indentiferなどに部品の名称を設定することでエラーメッセージの「0x7fde9f408970」こういうのを読みやすくできる。