AutoLayoutでiPhoneXのedge-to-edge対応

iPhoneXが新しく登場しましたが、従来の実装ではちゃんと全画面表示にならなかったり、逆にiPhoneXのSafeAreaからはみ出してしまう画面が出てくると思います。
その例と修正方法をいくつかまとめたいと思います。

### deprecatedになった `topLayoutGuide` と `bottomLayoutGuide`
`topLayoutGuide` と `bottomLayoutGuide` がdeprecatedになってしまいましたが、Xcode9では [safeAreaLayoutGuide](https://developer.apple.com/documentation/uikit/uiview/2891102-safearealayoutguide) に制約を貼ることになります。
では今まで `topLayoutGuide` と `bottomLayoutGuide`に貼っていた制約はXcode9でどうなるのかというと

<img src="https://qiita-image-store.s3.amazonaws.com/0/14751/5fbf562c-3bb6-724f-9ec8-f3d812104f8b.png" width=400>
Xcode8でつけた制約がそのまま貼られています。

そしてそれをiPhoneXで起動するとどうなるのか？
<img src="https://qiita-image-store.s3.amazonaws.com/0/14751/a2a84c1c-d1d4-84b9-2ffb-19ebe7a8fa29.png" width=160>
こうなります。
テーブルビューのコンテンツが下まで表示されず、SafeAreaで切られた見た目になっています。


これをbottomをsuperViewに対して貼るように修正するとちゃんと下までスクロールするようになります。
<img src="https://qiita-image-store.s3.amazonaws.com/0/14751/636ac39c-b823-f4f2-b889-63c6a41eb72e.png" width=400>

ちなみに[Human Interface Guideline](https://developer.apple.com/ios/human-interface-guidelines/overview/iphone-x/)に以下のように書いてありますが、テーブルビューを使う場合、`UITableViewController`を使えば勝手に全画面表示表示になってくれるので自分で意識する必要はありません。

> Most apps that use standard, system-provided UI elements like navigation bars, tables, and collections automatically adapt to the device's new form factor. Background materials extend to the edges of the display and UI elements are appropriately inset and positioned.

### コードで実装する場合
AutoLayoutを使わずframeを用いて以下のように書いて実装していた場合はiPhoneXでも全画面で表示されます。

```
let tableView = UITableView()
    
override func viewDidLoad() {
    super.viewDidLoad()
    tableView.delegate = self
    tableView.dataSource = self
    view.addSubview(tableView)
}
    
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    tableView.frame = view.bounds
}
```

ただし、ボタンをテーブルビューの下に配置して以下のようなUIを実装していた場合、iPhoneXではボタンがSafeAreaからはみ出してしまいます。

```
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    button.frame = CGRect(x: 0, y: view.frame.height - button.frame.height, width: view.frame.width, height:44)
        
    var tableFrame = view.bounds
    tableFrame.size.height -= button.frame.height
    tableView.frame = tableFrame

}
```
<img src="https://qiita-image-store.s3.amazonaws.com/0/14751/c93f03ec-7ede-e198-988a-f2cbc3a271d1.png" width=160>　<img src="https://qiita-image-store.s3.amazonaws.com/0/14751/4db7ce5b-b0e3-c633-5602-8ce63d7c1b15.png" width=160>

これはSafeAreaを意識した実装に直す必要があります。
iOS11から登場した [safeAreaInsets](https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets) を使えばそのビューのSafeAreaのInsetsを取れるのでこの値を加味して計算すればちゃんとSafeArea内に収める事ができます。

```
override func viewDidLayoutSubviews() {
       
    button.frame = CGRect(x: 0, y: view.frame.height - button.frame.height - view.safeAreaInsets.bottom, width: view.frame.width, height:44)
        
    var tableFrame = view.bounds
    tableFrame.size.height = button.frame.origin.y
    tableView.frame = tableFrame
}
```
またAutoLayoutを使う場合は、iOS11から新たに追加した [constraintEqualToSystemSpacingBelow](https://developer.apple.com/documentation/uikit/nslayoutyaxisanchor/2866022-constraintequaltosystemspacingbe) を使っても簡単に実装ができます。

```
let safeArea = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
    // ボタンの制約
    button.centerXAnchor.constraintEqualToSystemSpacingAfter(view.centerXAnchor, multiplier: 1.0),
    button.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1.0),
    button.heightAnchor.constraint(equalToConstant: 44),
    safeArea.bottomAnchor.constraintEqualToSystemSpacingBelow(button.bottomAnchor, multiplier: 1.0),
    // テーブルビューの制約
    safeArea.topAnchor.constraintEqualToSystemSpacingBelow(tableView.topAnchor, multiplier: 1.0),
    safeArea.leadingAnchor.constraintEqualToSystemSpacingAfter(tableView.leadingAnchor, multiplier: 1.0),
    tableView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1.0),
    tableView.bottomAnchor.constraintEqualToSystemSpacingBelow(button.topAnchor, multiplier: 1.0)
])
```

<img src="https://qiita-image-store.s3.amazonaws.com/0/14751/92711991-6af4-d64c-4618-37552df7ceed.png" width=160>

ただし、これだと結局全画面になってないし、これが見た目的にアリなのかはちょっと疑問ですね。
ボタンをフローティングにしてテーブルビューは下までスクロールするとか、ボタンの高さを広げたデザインにしてもいいかもしれません。

<img src="https://qiita-image-store.s3.amazonaws.com/0/14751/2f5363e4-9b67-f62d-fba9-ca714d9126eb.png" width=160>　　<img src="https://qiita-image-store.s3.amazonaws.com/0/14751/0e82fbdc-48b8-875e-fb56-b18653162a32.png" width=320>
<img src="https://qiita-image-store.s3.amazonaws.com/0/14751/ce44a849-2e13-08b9-0f73-db62b43d8dfb.png" width=160>　　<img src="https://qiita-image-store.s3.amazonaws.com/0/14751/b25883d2-77d7-f023-e592-2553d46468b6.png" width=320>


### まとめ
所感としては、1画面をedge-to-edge対応するには制約を付け替えるくらいでそんなに時間はかからなそうですが、画面がたくさんあるとめんどくさそうです。
また、画面によってはデザインの再考の必要性もありそうなので、他のアプリが同対応していくのかもちょっと気になります。