【SwiftUI App】WindowGroup の RootView を切り替える方法

iOS14 から採用されている SwiftUI App ライフサイクルでは以下のように WindowGroup の中にある View(ここでは ContentView)がアプリケーションの RootView になっています。

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

今回はこの RootView を切り替える方法について一例として紹介したいと思います。

WindowGroup の RootView を切り替える方法

@StateObject

今回のポイントは @StateObject 修飾子です。

以前より、@State 修飾子と言うものはありました。@State は、Int・String・Bool といった値型の変数の状態をViewと連携させるものでしたが、@StateObjectオブジェクト型(class型)の変数を扱う場合に使用します。

以下のサンプルでは、この @StateObject として宣言したオブジェクトが持つ enum を元に RootView を分岐する例を示します。

RootView 切り替え実装例

AppState シングルトンクラス

class AppState: ObservableObject {
    
    static let shared = AppState()
    private init() {}
    
    enum RootViews {
        case splash
        case main
    }
    @Published private(set) var rootView: RootViews = .splash
    
    func changeRootView(rootView: RootViews) {
        self.rootView = rootView
    }
}

ObservableObject プロトコルに準拠した AppState と言う名前のシングルトンクラスを作成します(SwiftUI らしさを考えると EnvironmentObject で表現しても良いかもしれません)。

enum でスプラッシュ画面(splash)とメイン画面(main)の列挙子を定義しました。この値で WindowGroup の RootView を切り替えます。

SplashView と MainView

struct SplashView: View {
    var body: some View {
        Text("Splash View")
            .foregroundColor(.blue)
            .onAppear() {
                DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
                    AppState.shared.changeRootView(rootView: .main)
                }
            }
    }
}

struct MainView: View {
    var body: some View {
        Text("Main View")
            .foregroundColor(.red)
    }
}

最初は、前者の SplashView が表示されています。そして表示から 2.0 秒後に AppStatechangeRootView で RootView に MainView 指定しています。

@StateObject と WindowGroup での分岐

@main
struct SampleApp: App {
    
    @StateObject private var appState = AppState.shared
    
    var body: some Scene {
        WindowGroup {
            switch appState.rootView {
            case .splash:
                SplashView()
            case .main:
                MainView()
            }
        }
    }
}

AppState.shared インスタンスを @StateObjectappState に紐づけ、WindowGroup ブロック内の switch で RootView を分岐させています。

@StateObject が付加されていなかったり、誤って @State にしてしまっていたりすると動作しませんので注意してください。

スプラッシュだけでなく、プッシュ通知等から最初に表示される画面からメイン画面に遷移する時などにも使えそうですね。

以上