取り急ぎUnityアプリをiPhoneX対応する

# はじめに

予約開始日に頑張った人にはそろそろiPhone Xが行き渡った頃だと思いますので、既存のUnityアプリをiPhone X対応する方法を書いておきます。

が、

**この対応でAppleの審査を通したわけではない**

のでご注意ください。まだ不十分な点があるかもしれません。「いいから早くiPhoneXで動かしてよー」と偉い人から言われた方に向けて、取り急ぎ、です。

※検証環境 Unity 2017.1.2p3 Xcode 9.1

# そのままだと…

実際には、特に何も対応せずそのままアプリをインストールしてもちゃんと動きます。が、その場合は画面の16:9の範囲外に黒い額縁が付きます。結果的にiPhone6,7,8で動かしているような見た目になります。

別にこれでいいのでは…とも思うんですが、各所の反応を見ますと、iPhoneXに最適化されていないというだけで評価を下げる人もいらっしゃいますし、Appleもいずれは非対応のアプリをRejectするでしょうし、対応は必須と思われます。

# 必要なもの

Appleのデベロッパサイトにはこう書かれています。

>Your app will run in Full Screen Display Mode on iPhone X if your project’s base SDK is set to iOS 11 and you have a Launch Storyboard or iPhone X launch image.

Update your apps for iPhone X.
https://developer.apple.com/ios/update-apps-for-iphone-x/

つまり、必要なのは

- iOS 11 SDK (= Xcode 9)
- LaunchStoryboard(LaunchScreen) or iPhoneX向けLaunchImage

のみなので、何もしてないのにフル画面で動いたという方もいらっしゃるのではないでしょうか。

Xcode9は普通にインストールすればいいとして、LaunchStoryboardとLaunchImageについては、ひとまずLaunchStoryboardを使用します(現行の正式リリース版UnityEditorにはiPhoneX用のLaunchImageを設定する項目がないため)。

ということで、PlayerSettingsの"iPhone Launch Screen"の"Launch screen type"が"Default"などLaunchScreenを使用する設定になっていることを確認してください。"None"ではLaunchImageを使用するため、額縁付きになってしまいます。

![LaunchScreen.png](https://qiita-image-store.s3.amazonaws.com/0/141731/34decbe5-e60e-a7fe-3836-0cf539666e04.png)

なお、Xcode9でiOS11SDKを使用しますが、従来通りiOS10以下をターゲットにすることもできるので、iPhoneX対応=iOS11以降専用になるわけではありません。

# 問題点

さて、これでビルドすればiPhoneXのフル画面でアプリを実行できるようになりますが、実機で見てみるといろいろ問題点があるのが分かると思います。

- 四隅が丸くなっている部分の画面が隠れる。
- センサーハウジング(FaceIDなどが入っている切り欠き)の下の画面が隠れる。
- ホームインジケーター(ホーム画面に戻るための目印)が見た目的にも操作的にも邪魔。
- 16:9以上の横長画面を想定していないのでUIのレイアウトが崩れる。

さぁ、どうしましょう？

# Unity側の対応

これらの問題点に対して、Unityから対策を含めたパッチリリースが出ています。その進行状況は以下のスレッドで告知されています。(Forumでこっそりやらなくてもとは思いますが…)

iPhone 8 / 8Plus / X support
https://forum.unity.com/threads/iphone-8-8plus-x-support.498078/

重要な部分を抜き出すと、

- Safe areas API (Screen.safeArea): 2017.2.0p2 (or maybe 2017.2.0p1 but this is not guaranteed) and 2017.3.0b7.
- Player settings to mark certain screen edges as "protected" (the system gestures will be deferred to the second swipe): 5.6.4p3, 2017.1.2p4, 2017.2.0p2, 2017.3.0b8.
- Player setting to hide home button indicator: 5.6.4p3, 2017.1.2p4, 2017.2.0p2, 2017.3.0b8.
- iPhone X legacy launch image support: 5.6.4p3, 2017.1.2p4, 2017.2.0p2, 2017.3.0b8.
- iOS Device generation enum fields for detecting these devices are being added and will appear into corresponding branches from 5.6 to 2017.x. Estimated to come in 1-2 weeks depending on branch.

という機能が実装予定のようです。ただ、すべてのバージョンに用意されるわけではなく、最低でも5.6.4, 2017.1.2, 2017.2.0, 2017.3.0のどれかにはアップデートする必要があります。

機能の内容的にはこんな感じ。

- セーフエリアAPI
 - UIなどの重要な情報を入れるべき画面領域を取得するAPI
- 画面隅のシステムジェスチャーの反応を遅らせる設定
 - 2回スワイプしないと通知センター等が出ないようにできる
- ホームインジケーターを消す設定
 - 常時表示ではなく、しばらくタッチがないと消えるようにできる
- iPhoneX用LaunchImageの設定
 - レガシーなLaunchImageを使わざるを得ない方向け
- iPhoneXの機種定義の追加
 - iOS.DeviceGeneration.iPhoneX というenumが追加された 

# LaunchImage

もはやあまり使っている人がいないと思われるLaunchImage。アプリ起動画面用の画像ですが、iPhoneX専用のタイプが追加されています。解像度としては1125x2436ドットですが、XcodeのDevicesでiPhoneXを繋いでスクリーンショットを撮って編集するのが手っ取り早いです。その画像をPlayerSettingsの"Legacy Launch Images"の"iPhone X /Retina"に設定してください。

![LaunchImage.png](https://qiita-image-store.s3.amazonaws.com/0/141731/6954f5b7-e3fa-bf77-1f99-3dd10e0ea3f2.png)

ただ、LaunchStoryboardを使った方が、画像を全機種分持つ必要もないので、そちらをおすすめします。その場合、この設定は必要なく、前述のとおり"Launch screen type"を"Default"にします。

# システムジェスチャーとホームインジケーター

あと、こういったPlayerSettingsが追加されています。

![iPhonXSettings.png](https://qiita-image-store.s3.amazonaws.com/0/141731/8e0247cc-7689-da1a-5dc9-c5d2b1e4add0.png)

文字がチェックボックスで隠れているあたりにパッチリリースっぽさを感じるところですが、"Defer system gestures on edges"と"Hide home button on iPhone"が追加された項目です。

機能は前述のとおりなんですが、これらを有効にして試してみると…

- ジェスチャーを遅らせる設定はホームインジケーターには無効。
- ホームインジケーター非表示時でもタッチすればすぐに機能する。

ということで、

**Appleは意地でもホームインジケーターを常時有効にしたいらしい**

ということが分かりました…。つまり、これらの設定ではホーム画面への意図しない遷移は防げません。額縁付きだとホームインジケーターは2回スワイプしないと機能しないんですけどね。そっちがデフォルトでもいいのに…。

# セーフエリア

となると、別の対策を考えねばなりません。といっても、UIをホームインジケーターにかぶらない位置に移動するくらいしか残された手はありません。そういった理由もあり、iOS11から「セーフエリア(安全領域)」という概念が導入されました。見た目的、操作的に重要な部分はこの中に入れてね、というものです。

逆に言うと、

**セーフエリア外は賑やかし程度の絵が出ていればOK**

です(たぶん)。つまり「iPhoneXの実質的な画面サイズはセーフエリア」と考えていいんじゃないでしょうか。

で、それを取得するためのAPIが実装されました。Screen.width, Screen.height内でのセーフエリアのRectが取得できます。

```UnityEngine.Screen
public static Rect safeArea;
```

しかし、上記のリリース告知にある通り、2017.2.0p2と2017.3.0b7以降でないと対応していません。5.6系と2017.1系の皆さん残念！！…ということで、現行のUnityでセーフエリアを取得するためのネイティブプラグインとサンプルが提供されています。

iOSSafeAreasPlugin
https://bitbucket.org/p12tic/iossafeareasplugin/src

単純に [UIView safeAreaInsets] でセーフエリアの座標を取得してUnityの座標系に変換するだけのプラグインなんですが、サンプルのSetCanvasBounds.csというスクリプトとテストシーンがなかなか興味深いです。

「セーフエリアを取得できるのはいいとして、それをどう使うか？」ということなんですが、このサンプルでは、Canvas直下にPanelを一つだけ置いて、他のUIはすべてPanelの下に置いています。そして、PanelのAnchorをセーフエリアに合わせて内側に縮小するだけ。

```SetCanvasBounds.cs(一部改変)
public RectTransform panel;      // Panel

Rect safeArea = GetSafeArea();   // NativePlugin

var anchorMin = safeArea.position;
var anchorMax = safeArea.position + safeArea.size;
anchorMin.x /= Screen.width;
anchorMin.y /= Screen.height;
anchorMax.x /= Screen.width;
anchorMax.y /= Screen.height;

panel.anchorMin = anchorMin;
panel.anchorMax = anchorMax;
```

こうすると、Canvasの設定は今までのままで、セーフエリアに合わせてUIの位置を変えることができます。頭いいな…というか、高機能なUnityのUIシステムをうまく使った実装と言えましょう。あとは、Canvas直下に大きめの画像を1枚置いてCanvasScalerを適切に設定すれば、セーフエリア外もいい感じに賑やかにできます。

# iOS.DeviceGeneration.iPhoneX

あと、iPhoneXを識別するためのこういったenumが追加されたんですが、正直、使用はおすすめしません。

iPhoneXの登場を受けて同じような画面サイズのAndroid端末も出てくるでしょうし、以後のiPhoneシリーズがすべてこの画面サイズに統一される可能性も高いので、よほどiPhoneX限定のバグでもない限り、セーフエリアAPIやCanvasScalerをうまく使って対応するのが、今後のことを考えると一番良いのではないかと思います。

# おわりに

という感じで、取り急ぎ。