react-native-splashscreenでスプラッシュスクリーン画像を全画面で表示する

## バージョン
react-native 0.63.3
react-native-splash-screen 3.2.0

## react-nativeのインストール

```sh
$ npx react-native init AwesomeTSProject --template react-native-$template-typescript
$ cd AwesomeTSProject
$ npm i
```

とりあえず起動するか確かめます。

```sh
$ npm run android
$ npm run ios
```

## [react-native-splashscreen](https://github.com/crazycodeboy/react-native-splash-screen)のインストール

```sh
$ npm i react-native-splash-screen
$ cd ios
$ pod install
```

## コード修正

### Android

```java:android/app/src/main/java/com/awesometsproject/MainActivity.java
package com.awesometsproject;
import android.os.Bundle; //追加
import com.facebook.react.ReactActivity;
import org.devio.rn.splashscreen.SplashScreen; // 追加

public class MainActivity extends ReactActivity {

  /**
   * Returns the name of the main component registered from JavaScript. This is used to schedule
   * rendering of the component.
   */
  @Override
  protected String getMainComponentName() {
    return "AwesomeTSProject";
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    SplashScreen.show(this);  // 追加
    super.onCreate(savedInstanceState);
  }
}
```

`android/app/src/main/res/layout`ディレクトリを作成。

`android/app/src/main/res/layout/launch_screen.xml`を作成。

```xml:android/app/src/main/res/layout/launch_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/launch_screen" android:scaleType="centerCrop" />
</RelativeLayout>
```

`android/app/src/main/res/drawable`ディレクトリを作成して`launch_screen.png`を置くだけです。

### iOS

```objective_c:ios/AwesomeTSProject/AppDelegate.m
#import "AppDelegate.h"

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "RNSplashScreen.h" // 追加

#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>

static void InitializeFlipper(UIApplication *application) {
  FlipperClient *client = [FlipperClient sharedClient];
  SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
  [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
  [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
  [client addPlugin:[FlipperKitReactPlugin new]];
  [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
  [client start];
}
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#ifdef FB_SONARKIT_ENABLED
  InitializeFlipper(application);
#endif

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"AwesomeTSProject"
                                            initialProperties:nil];

  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  [RNSplashScreen show]; // 追加
  return YES;
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

@end
```

```tsx:App.tsx
import React from 'react';
import {SafeAreaView, Text} from 'react-native';
import SplashScreen from 'react-native-splash-screen';

const App = () => {
  SplashScreen.hide(); // 追加
  return (
    <SafeAreaView
      style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
      <Text>React Native</Text>
    </SafeAreaView>
  );
};

export default App;
```


ここまでの設定はリポジトリのREADMEにも書いてあるし他の方も記事にしていたりするので、スムーズにできるのですがiOS・Androidの開発に慣れてない自分はここからが問題でした。


## スプラッシュスクリーンで使用する画像のサイズについて

あとはpngの画像を用意して画面いっぱいに画像を表示するだけですが、そもそもどういうサイズの画像を用意すればいいのかわかりませんでした。

### Android

[各種のピクセル密度をサポートする-代替ビットマップを提供する](https://developer.android.com/training/multiscreen/screendensities#TaskProvideAltBmp)

[これからAndroidアプリをつくるデザイナーのための基礎知識](https://note.com/ash/n/n66c3c4138abe) 

密度ごとに作るというのはわかりますがどのサイズで作ればいいのか。


[App Splash Screen Sizes](https://github.com/phonegap/phonegap/wiki/App-Splash-Screen-Sizes )

> - LDPI:
  - Portrait: **200x320px**
  - Landscape: **320x200px**
- MDPI:
  - Portrait: **320x480px**
  - Landscape: **480x320px**
- HDPI:
  - Portrait: **480x800px**
  - Landscape: **800x480px**
- XHDPI:
  - Portrait: **720px1280px**
  - Landscape: **1280x720px**
- XXHDPI:
  - Portrait: **960px1600px**
  - Landscape: **1600x960px**
- XXXHDPI:
  - Portrait: **1280px1920px**
  - Landscape: **1920x1280px**  

結局、上記のサイズを参考に作りました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/498cb901-cd33-6f6d-ac36-5170b5953163.png">


今回使用する画像はXDで1280x1920の画像を作成して、デザイン倍率xxxhdpiに設定してAndroid用に書き出ししました。
XDを使用していない場合はデザイナーさんに1280x1920で画像を作ってもらって[9-patch](https://romannurik.github.io/AndroidAssetStudio/index.html)で作成すればいいと思います。

#### 画像が切れて表示される

エミュレータで確認してみると画像が画面いっぱいに表示されず横が切れてしまいます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/60659c53-4f57-df05-9344-f031078c7f56.png" width=30%>

`android/app/src/main/res/layout/launch_screen.xml`の`android:scaleType="centerCrop"`を`android:scaleType="fitXY"`に変更します。

```xml:android/app/src/main/res/layout/launch_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/launch_screen" android:scaleType="fitXY" />
</RelativeLayout>
```

これで横が切れずに表示されるようになりました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/7db1f313-f7ff-6664-5134-ab8f8f1615ab.png" width=30%>

参考: [scaleTypeについて](https://qiita.com/alt_yamamoto/items/ab68a28e1fb6f843205b)

### iOS

[LaunchImageは明確に画像のサイズが指定されている](https://qiita.com/pe-ta/items/b3b754bde584abc98f44)のでわかりやすいですが、xcodeからImages.xcassetsで追加しようとするとLaunchImage(deprecated)と表示され、[現在はStoryBoardでLaunchScreenを作成する必要があります](https://developer.apple.com/jp/news/?id=03262020b)。

結局どのサイズを用意すればいいのかよくわかりませんが一番サイズの大きい1242x2688で書き出しすることにしました。

参考:
[スプラッシュを一枚絵の画像にしている場合のiPhone XS Max，iPhone XRの最適化対応](https://qiita.com/MilanistaDev/items/4fd30a1d2939b1cc0f16)
[【iOSアプリ】スプラッシュの登録、設定方法のメモ（iOS13対応）](https://swallow-incubate.com/archives/blog/20200513)  

#### storyboardで画像を設定する

`open ios/プロジェクト名.xcworkspace`でxcodeを開きます。
Images.xcassetsを選択してImage Setを追加・LaunchScreenに名前を変更して画像をセットします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/2dd9648f-43db-3e67-b4d7-134047542272.png">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/9b8b1204-4812-bb84-1e2d-d61b06579bc0.png">

`LaunchScreen.storyboard`を開いて不要なテキストは削除します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/9a831ff9-e45d-81c6-6725-c09ebe08c8f7.png">
`Shift + Command + L`でオブジェクトライブラリを開いてImages.xcassetsのLaunchScreenをドラッグ&ドロップで追加します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/8426419a-430b-439e-d1ea-656a1f7f8c8a.png">

縦横比の問題で画像全体が画面いっぱいに表示されないときはImageViewのAttributeからContentModeを変更して調整します。
Aspect Fit・Aspect Fillだと縦が切れてしまうのでScall To Fillを選択しました。また今回のようにstoryboardに画像一枚を画面いっぱいに表示する場合、横や縦に伸縮して表示されるので注意が必要です。

##### Constraintsを設定

iPhone12 Pro MaxのViewでは全画面で表示されますが他のデバイスでは画面からはみでて表示されてしまうので`+Add New Constraints`で上下左右に0の制約をつけます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/c72b2861-1db6-101f-4f1c-287c3f46be10.png">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/2ff63ff5-afd4-7303-1c04-0c4317e56a5d.png">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/2b177a4d-070b-5986-990e-330320fcb5de.png">
これでどの画面サイズでも画面いっぱいに画像が表示されるはずです。iPad対応が必要な場合は、Images.xcassetsのLaunchScreenでDeviceをUniversalからiPhone・iPadに変更して適切な画像を設定すればよいでしょう。

※スプラッシュスクリーンの設定がシミュレータに反映されない場合は、キャッシュの問題で反映されていない可能性があるので、`Simulatorメニュー→Device→Erase All Content And Settings`でシミュレータをリセットするなりMacを再起動するとうまくいったりします。

参考:
[iOS/Android アプリ開発で必要な画像のサイズ一覧 (アイコン・スプラッシュ等)](https://backapp.co.jp/blog/11573/) 
[SwiftにおけるUIImageView・contentMode 別の画像表示](https://blog.officekoma.co.jp/2016/10/swiftuiimageviewcontentmode.html)


## おわり

ライブラリ追加して画像を表示するだけですがイメージ通りに表示されるまで結構大変でした。画像に対してデバイスの画面サイズが複数存在するので、縦横が伸縮されて表示されてしまい1枚絵の画像を全画面表示にして、どのデバイスでも見た目良く表示するのは難しいです。