[Xcode 12] アプリの起動について変更になった部分まとめ

Xcode12からAppDelegate.swiftとSceneDelegate.swiftファイルを使わないアプリ起動が選べるようになりました。そんなアプリの起動まわりをまとめた記事です。
2020.10.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Xcode 12からAppDelegate.swiftとSceneDelegate.swiftファイルを使わないアプリ起動が選べるようになりました。

また、Swift5.3からアプリのエントリーポイントを指定できる@main属性が追加されました。

SE-0281: @main: Type-Based Program Entry Points

今回は、そんなアプリの起動まわりで変更になった部分を、簡単にではありますがまとめてみました。

変更になった部分

Xcode 12でプロジェクトを新規作成するとLifecycleという項目が追加されています。

こちらの選択肢ですが、InterfaceをSwiftUIにするとUIKit App Delegateの他にSwiftUI Appが選べるようになります。

生成されるファイルを比べてみる

それぞれ選択した際に初期に生成されるファイルが違います。

UIKit App Delegate の時は、旧来通りのAppDelegate.swiftとSceneDelegate.swiftファイルが生成されるのに対し、

SwiftUI Appの場合は<プロジェクト名>App.swiftファイルが生成されます。

info.plistを比べてみる

それぞれ生成されたプロジェクトのinfo.plistを比べてみると、Application Scene Manifest という項目の内容が違っています。

SwiftUI Appの場合はEnable Multiple Windows が YES なのに対し、UIKit App Delegateの方は NO になっており、Scene Configurationという項目が追加されています。

試してませんが、info.plistを書き換えることで、最初に選んでないLifecycleの方に変更できそうな感じですね...

コードを比べてみる

次に、それぞれ生成されたコードを見てみます。

UIKit App Delegate (今まで通り)

AppDelegate

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    }
}

SceneDelegate.swift

import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        let contentView = ContentView()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
    }

    func sceneWillResignActive(_ scene: UIScene) {
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
    }

}

@UIApplicationMain が @main になっています。また、SwiftUIの場合はSceneDelegateのfunc scene(_ ... で対象のViewをUIHostingControlleでラップしています。

SwiftUI App

次に、SwiftUI Appを選択した時の<プロジェクト名>App.swiftファイルの中身を見てみます。 ここではLifeCycleSwiftUIというプロジェクト名にしています。

LifeCycleSwiftUIApp.swift

import SwiftUI

@main
struct LifeCycleSwiftUIApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

短くてシンプルになっている印象です。

SwiftUI Appで起動時の処理などが必要な場合

ScenePhaseを使う方法とUIApplicationDelegateAdaptorを使う方法の2パターン存在する様です。

ScenePhase

@Environment(.scenePhase)を追加することにより、アプリのアクティビティ状態を取得することが出来ます。

import SwiftUI

@main
struct LifeCycleSwiftUIApp: App {
    @Environment(\.scenePhase) private var scenePhase
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: scenePhase) { scene in
            switch scene {
            case .active:
                print("scenePhase: active")
            case .inactive:
                print("scenePhase: inactive")
            case .background:
                print("scenePhase: background")
            @unknown default: break
            }
        }
    }
}

UIApplicationDelegateAdaptor

@UIApplicationDelegateAdaptorを使用することで、既存のAppDelegateを利用することができるようになります。

import SwiftUI

@main
struct LifeCycleSwiftUIApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        return true
    }
    
    // 必要に応じて処理を追加
}

さいごに

起動時の処理がシンプルなアプリに関しては、LifecycleをSwiftUI Appにするとコードが少なくなって楽だなと思いました。 逆に色々と複雑な対応が必要なアプリに関しては、UIKit App Delegateの方で作った方が良い場合もありそうな印象を受けました。