🐣

雰囲気でLaunchScreenをやっていた

2024/04/13に公開

はじめに

「LunchScreen? アプリ起動するときにロゴとか出てるあの画面でしょ?」

そのくらいの認識で、UIKitならLaunchScreen.storyboardでちょっと弄れるくらいは知っていたが、それほどちゃんと向き合ったことはなかった。
俺たちは雰囲気でLaunchScreenをやっている

LaunchScreenについて

まず、LaunchScreenとMainWidowは別物。(これを混同していて悩んだ)
LaunchScreenは起動画面(オレンジ)であり、MainWindowは起動後に最初に表示される画面(グレー)のことだ。

LaunchScreenとMainWidow

https://developer.apple.com/jp/design/human-interface-guidelines/launching

HIGによると、AppleにおいてはSplashScreenは推奨されず、あくまでLaunchScreenはLaunchScreenで、SplashScreenは存在しない。

詳しくはHIGを読むと良いが、知らなかったポイントとしては以下の2点があった。

  • ブランディングやオンボーディングをするところではない
  • アプリが一瞬で立ち上がっているように見せるのが大事

起動画面でロゴをアニメーションしてるのカッコいい〜と思っていたが、あれ良くなかったんだ...。

ちなみに、Android(MD2)ではブランディングもしよーぜ!と言っている。
https://m2.material.io/design/communication/launch-screen.html

ふぅ〜ん?
とりあえずLaunchScreenがどういうものなのかは分かったので、裏側を見ていく。

Development

アプリの起動時に何が起こっているのか

https://developer.apple.com/documentation/uikit/app_and_environment/responding_to_the_launch_of_your_app
https://developer.apple.com/documentation/uikit/app_and_environment/responding_to_the_launch_of_your_app/about_the_app_launch_sequence

LaunchScreenが表示されている間、以下のAppDelegateのメソッドが実行されている。

  • application(_:willFinishLaunchingWithOptions:)
  • application(_:didFinishLaunchingWithOptions:)

上記のドキュメント About the app launch sequence の図がとてもわかりやすい。↓

この Launch time に LaunchScreen が表示されているようだ。

さらに、iOS13以降ではSceneDelegateが登場し、MainWindowが表示されるまでの間にもう少しステップが追加されている。
登場以前はAppDelegateの application(_:willFinishLaunchingWithOptions:) のタイミングでMainWindowに使うViewControllerの生成と指定を行っていたようだが、登場以降はSceneDelegateの scene(_:willConnectTo:options:) で行うようになった。

SceneDelegate登場後の起動シーケンスについては以下の神記事がわかりやすかった。
https://qiita.com/omochimetaru/items/31df103ef98a9d84ae6b

なぜMainWindowに言及しているのかというと、擬似SplashScreenを作って起動時にあれこれ処理を行う場合があるからだ。
(ネットでSplashScreenの設定方法を調べていると、MainWindowに擬似SplashScreenを設定することとLaunchScreenを設定することが同列に書かれていることがままあり、混乱した)

コードだけで画面を作りたかったり、まぁ何か様々な事情でただのLaunchScreenだけでは足りないことがある時(HIG違反だがオンボしたかったりとか)に擬似SplashScreenを作る。
擬似SplashScreenは通常のViewControllerと同じように画面を作り、行いたい処理が終わったら次の画面に遷移するViewControllerをMainWindowに設定すれば良い。ここの詳細については割愛する。

AppDelegateとSceneDelegate合わせたらだいたいこんな感じだと思う。

LauchScreenの設定方法

https://developer.apple.com/documentation/xcode/specifying-your-apps-launch-screen

LaunchScreenの設定方法は以下の2種類ある

  • 画像や背景色をInfo.plistで指定する
  • storyboardで画面を作る

画像や背景色をInfo.plistで指定する

https://developer.apple.com/documentation/bundleresources/information_property_list/uilaunchscreen
Info.plistの UILaunchScreen キーを使うと、storyboardを使わずにLaunchScreenを構成できる。

Info.plistのUILaunchScreenの設定

  • Launch Screen
    • Background color: 背景色
    • Image Name: 画像
    • Image respects safe area insets: SafeAreaに収めるか
    • Show Navigation bar: ナビゲーションバーを表示するか
    • Show Tab bar: タブバーを表示するか
    • Show Toolbar: ツールバーを表示するか

HIGで言及されていたように、起動時に表示する画面のハリボテになるように作るのが望ましいので、バーの表示の設定などがある。

このキーを作るだけ作って何も設定しないこともできる。その場合は、OSのAppearance依存の背景色が適用された真っ白/真っ黒な画面がLaunchScreenとなる。

storyboardでLaunchScreenを構成する

https://developer.apple.com/documentation/bundleresources/information_property_list/uilaunchstoryboardname

新規プロジェクトをstoryboardで作った時は初期ファイルにあるLaunchScreen.storyboardを編集する。
自分で作る場合は、任意のstoryboardを作ったあと、Info.plistの Launch screen interface file base name キーにstoryboardの名前を指定する。

Info.plistのUILaunchStoryboardNameの設定

このキーに存在しないstoryboardを指定しても動くが、その場合は真っ黒な画面がLaunchScreenとなる。(OSのAppearanceに依存しているわけでもなさそうなので、謎だ)

Info.plistにLaunchScreen系の設定は必ず追加する

Info.pilstに Launch Screen も Launch screen interface file base name も設定しなかった場合、アプリ全体の画面サイズが壊れる。

以下のオレンジの部分がアプリ全体の背景色だ。崩れている...。
画面サイズが壊れている

SwiftUIで新規プロジェクトを作成すると、初期値として何も設定されていない Launch Screen がある。
SwiftUIの新規プロジェクトのInfo.plist
特に何も設定する必要がないときは↑のようにしておくと良いだろう。

MainWindowの設定方法(=擬似SplashScreenの設定方法)

https://developer.apple.com/documentation/uikit/uisceneconfiguration

MainWindowの設定はInfo.plistの SceneConfigurationWindow Application Session Role から行える。

MainWindowがstoryboardの場合は Storyboard Name キーに指定する。

storyboardを使わずにコードでViewControllerを作成する場合は、 SceneConfiguration から Storyboard Name を削除し、SceneDelegateの scene(_:willConnectTo:options:) にて生成・Windowへの設定を行う。

SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let scene = (scene as? UIWindowScene) else { return }
        
        let window = UIWindow(windowScene: scene)
        let splashVC = SplashViewController()
        window.rootViewController = splashVC
        self.window = window
        window.makeKeyAndVisible()
    }

    // ...

MainWindowの設定は Targets > Info > Custom iOS Target Properties にも同様のキーで存在しているので、storyboardを消す際はこちらの設定からも削除する。

SwiftUIの場合

SwiftUIの場合、 @main がついているところのトップにあるViewがMainWindowに相当する。
擬似SplashScreenを作りたい場合、ここにViewを置き、処理が終わったら本来の画面に自動遷移するようにすると良いだろう。

App.swift
import SwiftUI

@main
struct testApp: App {
    var body: some Scene {
        WindowGroup {
            // ここ
            SplashView()
        }
    }
}

おわりに

自分の中では消化できたと思ったが、文章にまとめてみると概念の違いがちゃんと伝わるのか不安になってきた。
LaunchScreenとSplashScreenとMainWindowと擬似SplashScreenの違いが伝わっていることを祈る。

Discussion