既存の SwiftUI アプリのライフサイクルを iOS 14 の SwiftUI App に変更してみる

Xcode 11 を使って iOS 13 向けに作成されたSwiftUI のアプリだと AppDelegate と SceneDelegate のライフサイクルでアプリが作成されている。

Xcode 12 以降だと、プロジェクト作成時に SwiftUI のアプリのライフサイクルが選べるようになっている。

f:id:daisuke-t-jp:20201022013103p:plain:w500

選べるのは、以下だ。

既存の IUIKit App Delegate で作成した SwiftUI のアプリを iOS 14 以降の SwiftUI App のライフサイクルに変更する手順を以下にメモする。(iOS 13 は動作対象外になることに注意)

Info.plist

  • Info.plist の UISceneConfigurations の部分を削除する。

こうなっていたのが

 <key>UIApplicationSceneManifest</key>
    <dict>
        <key>UIApplicationSupportsMultipleScenes</key>
        <false/>
        <key>UISceneConfigurations</key>
        <dict>
            <key>UIWindowSceneSessionRoleApplication</key>
            <array>
                <dict>
                    <key>UISceneConfigurationName</key>
                    <string>Default Configuration</string>
                    <key>UISceneDelegateClassName</key>
                    <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                </dict>
            </array>
        </dict>
    </dict>

削除すると、こうなる。

 <key>UIApplicationSceneManifest</key>
    <dict>
        <key>UIApplicationSupportsMultipleScenes</key>
        <false/>
    </dict>

アプリのメインエントリを App プロトコルに変更

App から派生した struct を定義したファイルを作る。

WindowGroup に最初に始まる View を設定する。

import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView() // 最初に表示される View
        }
    }
}

既存の AppDelegate.swift, SceneDelegate.swift はプロジェクトから外して、ビルドされないようにする。

特に AppDelegate の @UIApplicationMain@main が両方存在すると、メインエントリが重複してしまいエラーになることに注意。

この状態でビルド→実行で、アプリ起動して View が表示されるところまで確認できる。

以下、Info.plist の UIApplicationSupportsMultipleScenes についての注意点。

既存のライフサイクルのアプリを、あたらしいアプリに置き換える際に UIApplicationSupportsMultipleScenes がもともと false の場合、インストール済みで起動しているアプリをあたらしいライフサイクルのアプリで上書きして実行すると、最初の View が表示されず黒い画面のままになる現象を確認した。(一度前のアプリを停止させてれば、画面は表示される)

UIApplicationSupportsMultipleScenes がもともと false で、ライフサイクル変更と共に true にして上書きインストールすると、上記の現象は起きなかった。

この時 UIApplicationSupportsMultipleScenes を変えるとアプリの動作にも影響があるため、変更する場合は気を付けたい。

UIApplicationSupportsMultipleScenes

既存の AppDelegate で実行している処理を新しいライフサイクルでも実現

AppDelegatedidFinishLaunchingWithOptions で初期化処理をしていたとする。たとえば Firebase とか。

class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Fireabase を初期化する
        FirebaseApp.configure() 

        return true
    }
}

これが新しいライフサイクルでも呼ばれるようにするには UIApplicationDelegateAdaptor というプロパティラッパーを使用して、従来の UIKit の UIApplicationDelegate の機能も利用できるようにする。

import SwiftUI

@main
struct TestApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView() // 最初に表示される View
        }
    }
}



class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Fireabase を初期化する
        FirebaseApp.configure() 

        return true
    }
}