iPhone X対応 ~Safe Areaの外側~

今年、iOSエンジニアを苦しめたであろうiPhone X対応。私も苦しめられた者の1人なのでその知見を投下しておこうと思います。この内容は[2017年 AKIBA.swift忘年回](https://classmethod.connpass.com/event/73341/)の[登壇資料](★)を文章化したものになります。

以下で出てくる環境はXcode9.2bata、Swift4.0です。AutoLayoutには[PureLayout](https://github.com/PureLayout/PureLayout)を部分的に使用しています。

サンプルコードはGitHubに上げています。
https://github.com/akatsuki174/OutsideSafeAreaLayout

# SafeAreaについておさらい

これから話の中で頻出するSafe Areaについて念のためさらっと確認です（とは言っても説明難しい）。

* WWDC2017で登場
* iOS11, Xcode9から使用可能
* top及びbottom layout guideのdeprecated化と入れ替わり

Apple公式ではSafe Areaの領域を以下のように説明しています。
![e5aca39a-f9a2-4ab8-9f45-08fd95fb845c.png](https://qiita-image-store.s3.amazonaws.com/0/100736/230268ec-e37a-d171-ae63-2a3711e08374.png)
出典：https://developer.apple.com/documentation/uikit/uiview/positioning_content_relative_to_the_safe_area

※以前[「Auto Layout Techniques in Interface Builder」というWWDC2017のセッションをざっくり訳した記事](https://qiita.com/akatsuki174/items/dd055b6401a79af7ccab)を書いたのですが、その中にもSafe Areaは登場しています。

# 今回実現したかったUI

<img width="250" alt="スクリーンショット 2017-12-08 22.37.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/50a98278-2597-8097-e3ca-6197dd3b1223.gif">

条件としては以下の通りです。

* 画面下部に、上下アニメーションするviewのセット（以下MenuViewと呼ぶ）を置く
* MenuViewの周辺の景観を乱さない

この実装をする上で3点ほど躓きポイントがあったので症状とその対策について書いていきます。

# こんな問題が起きたよ集

## 症状１：MenuViewがHome Indicatorと被る

### 状況

<img width="250" alt="スクリーンショット 2017-12-08 22.37.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/4cc4011c-b26d-d2b3-fc31-72659fb8c7ee.png"><img width="250" alt="スクリーンショット 2017-12-09 17.19.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/0e01bc79-af14-8d8a-e84b-257374eb1757.png">

ロックを解除する時に使う、画面下部にあるバーのことをHome Indicatorと呼ぶらしいですが、単純に画面下部にviewを貼り付けたらこれと被ってしまうという現象が発生しました。ボタン押せるけど、押せない。理想としてはSafeAreaの中にこのMenuViewを収めておきたいです。

### 元々の実装

MenuViewのbottomの制約は以下のように付けていました。

```swift:MenuView.swift
private var bottomInset: NSLayoutConstraint!
```

```swift:ViewController.swift
self.view.addSubview(menuView)
menuView.bottomInset = menuView.autoPinEdge(toSuperviewEdge: .bottom)
```

superview（ViewController.view）のbottomとMenuViewのbottomを合わせる制約を付けていました。

### 改修後の実装

```swift
if #available(iOS 11, *) {
    let guide = view.safeAreaLayoutGuide
    menuView.bottomInset = menuView.bottomAnchor.constraint(equalTo: guide.bottomAnchor)
    menuView.bottomInset.isActive = true
} else {
    menuView.bottomInset = menuView.autoPinEdge(toSuperviewEdge: .bottom)
}
```

iOS11以上であればSafeAreaの概念が登場しているのでそれをコードで取得して、superviewのbottomではなくSafeAreaのbottomに対して制約をかけます。そうすることでMenuViewのbottomがHome Indicatorの上に来てくれます。

## 症状２：viewの下にtable viewの内容がチラ見えする

### 状況

<img width="250" alt="スクリーンショット 2017-12-09 17.10.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/233ebfee-ff90-b31e-80f4-65ef486904ee.png">

MenuViewの下にtable viewが見えています。スクロールすると心なしか目がチカチカします。

### 元々の実装

storyboardを見てみたらbottomがSafeAreaのbottomに対してではなくSuperview（ViewController.view）のbottomに対してかかっていました。

### 改修後の実装

今回は解決策としてtable viewのbottomをSafeAreaのbottomにしてみます。

<img width="250" alt="スクリーンショット 2017-12-09 18.07.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/ed8d36e4-2a15-2c3d-d5e4-0dde3891d32a.png">

MenuViewの下の白い部分はViewControllerのviewの背景色になります。

## 症状３：MenuViewをフルで表示しない時にかっこ悪い

### 状況

MenuViewを下にしまった時、こんな表示になっています。

<img width="250" alt="スクリーンショット 2017-12-10 0.19.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/c008a435-bf4e-8df2-1027-2e78eadd1c36.png">

このスクショだとまだいいのですが、黄色の部分にコンテンツが表示されていることを想定してください。中途半端に見えていてなんだか微妙な気持ちになります。

### 改修後の実装

今回取った対応としては「↑↓」ボタンがタップされた時にアニメーションしつつalpha値を変更する、というものです。コードで表現すると以下のようになります。

```swift
UIView.animate(withDuration: 0.3,
               delay: 0.0,
               options: .curveEaseIn,
               animations: {
                   self.view.alpha = self.shouldShowFull ? 1.0 : 0.0
               },
               completion: nil)
```

これで目標としていたUIに辿り着けました🎉

# おまけ

## 症状

gifは貼れないので言葉で説明するんですが、以下の症状が発生したことがありました。

* 動的な制約をかけているセルのレイアウトがSafe Areaの境界付近でうにょうにょ動く
  * 画像/文言があるかないかで画像/文言領域を表示したりしなかったりするセル
  * Home Indicatorを通過する時にconstantの値が-x → 想定値、想定値 → -xにアニメーションするイメージ
* iPhone X以外は何も問題なく動く（それゆえ気づくの遅れた）

## 解決策

<img width="250" alt="スクリーンショット 2017-12-10 0.19.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/ca14095a-3b39-3343-7180-6da8152cb436.png">

該当のcell.xibのviewを選択し、Safe Area Layout Guideのチェックボックスを外すことで解決しました。

# まとめ：iPhone X対応のコツ

* レイアウトに関わる実装をした時は必ずiPhone Xでも確認する
* なんかおかしいと思ったらSafe Area Layout Guideのチェックボックスを外してみる
* Safe Areaの外側がどのような表示になっているべきか考える