iPhoneX SafeArea/Mergin対応

仕事でiPhoneX対応をしたのですが、あまりにどこから手をつけていいかわからず、泣きそうになりました。泣いてないけど。
小枝チョコを1日で40本食べるくらいには思いつめたので、そんな人が他にもいた時のために、メモっておきます。

概要

SafeAreaとは

Appleがガイドラインで明記している、iPhoneXで表示する際に必要な上下左右のスペースのこと。

https://developer.apple.com/ios/human-interface-guidelines/overview/iphone-x/
https://developer.apple.com/documentation/uikit/uiview/positioning_content_relative_to_the_safe_area

厳密には、単語は以下の定義?(個人的な解釈。正しいか不明)

  • SafeArea
    • 上部のステータスバーと下部のホームバーを除いたエリアのこと
  • Margin
    • 左右に必要なスペース
    • 画面を回転させるとサイズも変わる

SafeArea内に納めるためには

AutoLayoutでSafeArea内でどうやって表示するか設定すれば良さそう

部品のサイズをコードで計算して配置している場合は、
計算するときにSafeAreaの値を取得して、なんやかんやする。

対応

StoryBoard/xib

SafeArea

iPhone Xのセーフエリアやマージン幅について - Qiita
## 論理尺度比較図 ピクセル値ではなく、iOS ランタイムで扱われる論理尺度での比較図を作りました。単位は `pt` です。プログラミングにおいては実際にはこちらの座標系が利用されるからです。

SafeArea内に良きように収めてもらうために、AutoLayoutを設定する。

対応前。
濃い緑の部分がSafe Areaで、薄い緑の部分が、Safe Areaからはみ出している部分。

赤枠のUse Auto Layoutと、Use Safe Area Layout Guideにチェックを入れる。

Safe Areaに収まるように要素を配置し直す。
(これ全てのページやるの?!!!めんどう…)

Constraints = 制約をつける。

赤線で I 文字のようになっている部分をクリックして、設定画面を出す。
基準となる箇所を、Safe Areaにする。

Add * Constraints をクリックする。
これをしないと制約が設定されないので注意。

オレンジ枠の場所で、制約がちゃんと設定されていることを確認。

ナビゲージョンバーが上部にある場合

上記の手順で対応したところ、iOS11では期待通りに表示されるが、
iOS10だと、思った位置に表示されない(配置した要素がナビゲーションバーに被ってしまう)事象が発生。

↓

https://qiita.com/yaslam/items/4901a490ecf2812534a3
これを参考に解決。

ナビゲーションバーやツールバーがある場合は、
Constraintsの設定のところで、

  1. SuperViewを起点にして制約を設定して、
  2. Constraints to Margins にチェックを入れて、
  3. Add Constraints

すると、期待通りに表示される。

Margin

Constraintsの設定のところで、Constraints to Margins にチェックを入れて、Add Constraintsする。んだと思う。
試してないので本当にそれでできるのかは不明。

コード

制約をつけたい部分のAnchorに対し、 constraintEqualToAnchor で基準となるAnchorを指定し、activeにする。

SafeArea

self.webViewの上辺をSafe Areaに合わせた例。

[self.webView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor].active = YES;

self.view.safeAreaLayoutGuideに合わせるように指定する。

Margin

self.webViewの左辺(leading)にMarginを持たせた例。

[self.webView.leadingAnchor constraintEqualToAnchor:self.view.layoutMarginsGuide.leadingAnchor].active = YES;

https://qiita.com/yimajo/items/10f16629200f1beb7852
self.view.layoutMarginsGuide で、必要なマージンが取得できるようなので、それに合わせるように指定。


実装

最終的にはこんな感じ。
iOS10以下も対応必要があるので、分岐させています。

Before
[self.view addConstraints:@[
                            [NSLayoutConstraint constraintWithItem:self.webView
                                                         attribute:NSLayoutAttributeTop
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self.topLayoutGuide
                                                         attribute:NSLayoutAttributeBottom
                                                        multiplier:1.0
                                                          constant:0],
                            [NSLayoutConstraint constraintWithItem:self.webView
                                                         attribute:NSLayoutAttributeLeft
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self.view
                                                         attribute:NSLayoutAttributeLeft
                                                        multiplier:1.0
                                                          constant:0],
                            [NSLayoutConstraint constraintWithItem:self.webView
                                                         attribute:NSLayoutAttributeRight
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self.view
                                                         attribute:NSLayoutAttributeRight
                                                        multiplier:1.0
                                                          constant:0],

                            [NSLayoutConstraint constraintWithItem:self.webView
                                                         attribute:NSLayoutAttributeBottom
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self.Button
                                                         attribute:NSLayoutAttributeTop
                                                        multiplier:1.0
                                                          constant:-43]
                            ]];
}
After
if (@available(iOS 11, *)) {
    [self.webView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor].active = YES;
    [self.webView.leadingAnchor constraintEqualToAnchor:self.view.layoutMarginsGuide.leadingAnchor].active = YES;
    [self.webView.trailingAnchor constraintEqualToAnchor:self.view.layoutMarginsGuide.trailingAnchor].active = YES;
    [self.webView.bottomAnchor constraintEqualToAnchor:self.Button.topAnchor constant:-43].active = YES;
} else {
    [self.view addConstraints:@[
                            [NSLayoutConstraint constraintWithItem:self.webView
                                                         attribute:NSLayoutAttributeTop
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self.topLayoutGuide
                                                         attribute:NSLayoutAttributeBottom
                                                        multiplier:1.0
                                                          constant:0],
                            [NSLayoutConstraint constraintWithItem:self.webView
                                                         attribute:NSLayoutAttributeLeft
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self.view
                                                         attribute:NSLayoutAttributeLeft
                                                        multiplier:1.0
                                                          constant:0],
                            [NSLayoutConstraint constraintWithItem:self.webView
                                                         attribute:NSLayoutAttributeRight
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self.view
                                                         attribute:NSLayoutAttributeRight
                                                        multiplier:1.0
                                                          constant:0],

                            [NSLayoutConstraint constraintWithItem:self.webView
                                                         attribute:NSLayoutAttributeBottom
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self.Button
                                                         attribute:NSLayoutAttributeTop
                                                        multiplier:1.0
                                                          constant:-43]
                            ]];
}

こんな感じに変更。

高さ

サイズや位置を計算している箇所には、下記で対応。

現在表示されているバーの値を取得するには以下

// ステータスバー
[UIApplication sharedApplication].statusBarFrame.size.height;

// ナビゲーションバー
self.navigationController.navigationBar.frame.size.height;

Tips

Tableのアニメーションがなんか変なとき

iOS11 + Xcode9.0でedgesForExtendedLayoutの値を空にしていると、UITableViewのドリルダウンでアニメーションが崩れる - Qiita
# 背景 iOS7からUIViewControllerへ追加されたプロパティに、**edgesForExtendedLayout**があります。 - [edgesForExtendedLayout - UIViewControlle...