これからの「iPhoneX」の話をしよう。 - UITableView、UICollectionView編

# はじめに
この投稿は[AbemaTV Advent Calendar 2017](https://adventar.org/calendars/2216) 14日目の記事です！

今回は、現在[AbemaTV](https://abema.tv/) iOSチームのメンバーの一人である[cokaholic](https://github.com/cokaholic)こと、辰己が担当します。

以前、[【随時更新】iPhoneX完全対応マニュアル](https://qiita.com/cokaholic/items/6a8ee3852c8ed28ea2aa) という投稿を書いたのですが、その中では触れられていない、これから新規でiOSアプリを開発する方のための**具体的なiPhoneX対応AutoLayoutの使い方**をご紹介しようと思います。
今回は普段よく使う`UITableView編`と`UICollectionView編`です！

# UITableView編

まずは、UITableViewのStoryboard上におけるAutoLayoutの使い方について話そうと思います。

Xcode9からはStoryboard内でUIViewControllerを作成すると、デフォルトで **Use Safe Area Layout Guides** にチェックが入っており、初めから **SafeArea (UILayoutGuide)** が追加されています。

<img width="300" alt="UseSafeAreaLayoutGuides" src="https://qiita-image-store.s3.amazonaws.com/0/46227/29e95b20-f753-3e5e-3371-c00b4c2eb8a8.png">

そして、適当に中身を見繕ったUITableViewに、以下のように **SafeAreaの各Edgeに対して制約を追加した場合** と **SuperView(今回はUIViewControllerのview)の各Edgeに対して制約を追加した場合** だと、どのような見た目になるでしょうか？

| SafeAreaの各Edgeに対して制約を追加 | SuperViewの各Edgeに対して制約を追加 |
|---|---|
| ![SafeArea](https://qiita-image-store.s3.amazonaws.com/0/46227/223bfc79-531e-2ef5-c9dc-e7dd00a952f0.png) | ![SuperView](https://qiita-image-store.s3.amazonaws.com/0/46227/9a64c78f-0966-6242-ba43-15df410f7be8.png) |

ちなみに、 SuperViewへの制約の追加方法は、SafeAreaに制約を追加した後、追加した制約のFirstItemをSafeAreaからSuperViewに変更すれば適用することができます。
<img width="300" alt="スクリーンショット 2017-12-14 3.46.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/46227/fb136e7f-6790-34a6-21ac-5f6818bf54ea.png">

## SafeAreaの各Edgeに対して制約を追加した場合

| Portrait | Landscape |
|---|---|
| ![tate](https://qiita-image-store.s3.amazonaws.com/0/46227/95f2cc6f-a9d3-c88c-1273-0451014c9b80.png) | ![yoko](https://qiita-image-store.s3.amazonaws.com/0/46227/e305d1bf-6840-74c3-e5ad-ed3fe26ebd6f.png) |

SafeAreaに対して制約を追加した場合、UITableViewのフレームサイズはSafeArea内に収まります。
そのため、Portraitの際には上：44pt、下：34pt、Landscapeの際は左右:44pt, 下:21pt がフレーム外となり、切れて表示されます。
しかし、これでは見た目が悪すぎますし、iPhoneXの凹みを活かしきれていないレイアウトになってしまっています。

## SuperViewの各Edgeに対して制約を追加した場合

| Portrait (Top) | Landscape (Top) |
|---|---|
| ![tate](https://qiita-image-store.s3.amazonaws.com/0/46227/80755af4-4b03-aac4-1687-4c8d435f160a.png) | ![yoko](https://qiita-image-store.s3.amazonaws.com/0/46227/301bad26-0786-a0e1-084e-2a31abded6da.png) |

| Portrait (Bottom) | Landscape (Bottom) |
|---|---|
| ![tate](https://qiita-image-store.s3.amazonaws.com/0/46227/0786f27d-18c3-2e2a-c281-5b4f5cd3e3cd.png) | ![yoko](https://qiita-image-store.s3.amazonaws.com/0/46227/64030790-0af6-8b3a-caee-a3537868725c.png) |


続いて、SuperViewであるUIViewControllerのviewの各Edgeに対して制約を追加した場合、UITableViewのフレームサイズは画面全体に広がります。
ですが、各画面に注目すると、 **上部と下部にセーフエリア分のinset** が設けられていることがわかります。
これは、UITableViewの親クラスであるUIScrollViewの **contentInsetAdjustmentBehavior** プロパティによるものです。
デフォルトの`Automatic`を指定しておけば、Viewの各Edgeに制約を追加した際にそのViewの`safeAreaInsets`の値に応じて自動で上下にセーフエリア分のinsetを追加してくれます。
Storyboard上とコード上で指定する場合はそれぞれ以下のようになります。

### Storyboard上で指定する場合
<img width="300" alt="UseSafeAreaLayoutGuides" src="https://qiita-image-store.s3.amazonaws.com/0/46227/b6c3867f-be1a-2174-33e6-545326032ed6.png">

### コード上で指定する場合
```swift
// iOS11からしか指定できないので、iOS10以下もサポートする場合は分岐を追加
if #available(iOS 11, *) {
    tableView.contentInsetAdjustmentBehavior = .automatic
}
```

さらに、Landscapeの際の画面に注目すると、 **UITableViewのセルの左右にセーフエリア分のinset** が設けられていることがわかります。
これは、UITableViewの **insetsContentViewsToSafeArea** プロパティによるものです。
`insetsContentViewsToSafeArea`を有効にしておけば、Viewの各Edgeに制約を追加した際にそのViewの`safeAreaInsets`の値に応じて自動でセルの左右にセーフエリア分のinsetを追加してくれます。
Storyboard上とコード上で指定する場合はそれぞれ以下のようになります。

### Storyboard上で指定する場合
<img width="300" alt="UseSafeAreaLayoutGuides" src="https://qiita-image-store.s3.amazonaws.com/0/46227/45e65d03-83bc-a91b-06f8-4a0d75645bc5.png">

### コード上で指定する場合
```swift
// iOS11からしか指定できないので、iOS10以下もサポートする場合は分岐を追加
if #available(iOS 11, *) {
    tableView.insetsContentViewsToSafeArea = true
}
```

# UICollectionView編

続いて、UICollectionViewのStoryboard上におけるAutoLayoutの使い方について話そうと思います。

適当に中身を見繕ったUICollectionViewに、先ほどと同じく以下のように **SafeAreaの各Edgeに対して制約を追加した場合** と **SuperView(今回はUIViewControllerのview)の各Edgeに対して制約を追加した場合** だと、どのような見た目になるでしょうか？

| SafeAreaの各Edgeに対して制約を追加 | SuperViewの各Edgeに対して制約を追加 |
|---|---|
| ![SafeArea](https://qiita-image-store.s3.amazonaws.com/0/46227/e45cbbca-8fa3-5212-c562-260ada1ade80.png) | ![SuperView](https://qiita-image-store.s3.amazonaws.com/0/46227/0e8f4c50-f83f-fb6b-a8df-b4a373de2d6f.png) |

## SafeAreaの各Edgeに対して制約を追加した場合

| Portrait | Landscape |
|---|---|
| ![tate](https://qiita-image-store.s3.amazonaws.com/0/46227/7bf9a9ee-db46-9cb5-cc53-801033fcaaf2.png) | ![yoko](https://qiita-image-store.s3.amazonaws.com/0/46227/f923611f-5dcd-4381-4084-d79ff48da2d8.png) |

SafeAreaに対して制約を追加した場合、UICollectionViewのフレームサイズはSafeArea内に収まります。
そのため、Portraitの際には上：44pt、下：34pt、Landscapeの際は左右:44pt, 下:21pt がフレーム外となり、切れて表示されます。
またしてもiPhoneXの凹みを活かしきれていないレイアウトになってしまっています。

## SuperViewの各Edgeに対して制約を追加した場合

| Portrait (Top) | Landscape (Top) |
|---|---|
| ![tate](https://qiita-image-store.s3.amazonaws.com/0/46227/932b927c-1297-3b41-e135-6fd142c44a2e.png) | ![yoko](https://qiita-image-store.s3.amazonaws.com/0/46227/5168963e-f870-2246-82a3-35e7549f6be2.png) |

| Portrait (Bottom) | Landscape (Bottom) |
|---|---|
| ![tate](https://qiita-image-store.s3.amazonaws.com/0/46227/83aa8c0d-9932-c4bd-6843-475e67ee057c.png) | ![yoko](https://qiita-image-store.s3.amazonaws.com/0/46227/e525790a-fde6-3297-5e50-671c399fb40d.png) |

続いて、SuperViewであるUIViewControllerのviewの各Edgeに対して制約を追加した場合、UICollectionViewのフレームサイズは画面全体に広がります。
ですが、各画面に注目すると、 **上部と下部にセーフエリア分のinset** が設けられていることがわかります。
これも先ほどと同じく、UICollectionViewの親クラスであるUIScrollViewの **contentInsetAdjustmentBehavior** プロパティによるものです。

しかし、今回はUITableViewの場合と比べて、`Landscape`の表示に大きな違いがあります。
そうです、**Landscape時のcontentViewに対する左右のセーフエリアマージン** が無いのです。
これでは左右のマージン分はみ出してしまう...美しくない...

でもご安心を。ちゃんと対応する方法は用意されています！

### 解決方法

具体的な解決方法としましては、UICollectionViewFlowLayoutの **sectionInsetReference** プロパティを変更することで解決します。
まず、Storyboard上では指定できないため、以下のようにIBOutletで`UICollectionViewFlowLayout`を接続します。

<img width="750" alt="Bind" src="https://qiita-image-store.s3.amazonaws.com/0/46227/39e9e96d-7b39-8834-7d55-1fdf0760f368.png">

次に、接続した`flowLayout`を以下のように書き換えましょう。
ここのdidSet内にて、`sectionInsetReference`に **.fromSafeArea** を指定することで完了です。

```swift
@IBOutlet weak var flowLayout: UICollectionViewFlowLayout! {
    didSet {
        // iOS11からしか指定できないので、iOS10以下もサポートする場合は分岐を追加
		if #available(iOS 11, *) {
        	flowLayout.sectionInsetReference = .fromSafeArea
       	}
    }
}
```

実行してみると、以下のように無事にcontentViewの左右にセーフエリアマージンができていることがわかります。

<img width="750" alt="スクリーンショット 2017-12-14 14.53.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/46227/73d318ec-046a-7e83-2a0d-43036df76cdc.png">

# まとめ
今回はここまでです！
UITableViewもUICollectionViewも基本は**SuperViewの各Edgeに対して制約を貼ること**で、iPhoneXでも違和感の無い、凹みを活かしたレイアウトが可能です。

最後に、私はiPhoneX対応をがっつり担当したのですが、**普段はiPhone8を使用していますw**
CPUも高速になり、反応速度も大幅に向上したTouchIDはすごく便利ですね（ニッコリ）

以上です！
最後まで読んでいただき、ありがとうございました！

# 参考リンク
- [UIKitのAPI Diffから見る Safe Area - iOS LT #32 - Speaker Deck](https://speakerdeck.com/kazuhiro4949/uikitfalseapi-diffkarajian-ru-safe-area-ios-lt-number-32)