Pure SwiftUI App Life Cycle

去年のWWDC2019で発表されたSwiftUI。 待望の Swift製のUIライブラリでしたが、実際のところはUIHostingController上に新しいSwiftUIのViewをのせる方式で、過去互換性を守っていました。

そうするしかないだろうとは思ってたんですけど、中途半端さにすっきりしない気分の方も多かったと思います。

iOS14では、いよいよ、アプリのすべてのUIをSwiftUIで作ることができるようになりました。 UIViewControllerにもStoryboardにもAutoLayoutにもAppDelegateにも、window.makeKeyAndVisibleにもさよならです。

今までの古いコードを全部捨てて新しく書き直せるのかと思うと、ちょっとドキドキしますね。

(この記事の内容はXcode 12.0 beta 2 (12A6163b) で調査した結果ですが、実際に14.0が正式にリリースされる際には変更になる可能性が高いです。最新のXcodeで確認してくださいね。)

Pure Swift UI の前提条件

Pure Swift UIを使うには、target version を14.0にする必要があります。

iOS13以下のバージョンに対応する必要がある場合には使えないので、現実のプロジェクトに今すぐ適用するのは難しいかもしれません。

ただ、Pure SwiftUIにするとどう変わるのかを確認しておくと、今からアプリの将来設計を考えておく助けになるかと思います。

Pure Swift UI にするには

新規でPure Swift UI のプロジェクトを作る場合には、Xcode12を使ってください。

新規プロジェクトの設定ダイアログの中にLife Cycleという項目があります。 ここでSwiftUI Appを選ぶと Pure SwiftUIのアプリになり、UIKit App Delegateを選ぶと今までの App Delegate を使った構成になります。

アプリの起動時の処理

今までは、AppDelegate(UIApplicationDelegate)didFinishLaunchingWithOptionsで起動時の処理を行なっていましたね。

PureSwiftUIでは、App Protocolを継承する構造体(!)がその役目を担います。

現状では、その構造体のinit()で起動時処理をやるしかなさそうです。

試してみたところ、@Environmenで取得できる値が、init()で取得した値とSceneやViewで取得した値とでと異なっていることもありましたので、若干注意が必要です。

AppleのApp Life Cycle の公式ドキュメントには、SwiftUI Appについて記述がないので、このあたりの処理は今後変更される可能性もありそうです。 https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle

@main
struct MyPureSwiftApp: App {

    @SceneBuilder
    var body: some Scene {
            WindowGroup {
                ContentView()
            }

    init(){
        print(" initializing...")
    }

}

アプリがバックグラウンドになった時の処理

バックグラウンド/フォアグラウンドの処理は、iOS13で大きく変更が入っていますよね。(以前の構成のままでも特に問題はないので、新構成に対応した人は多くはないと思いますが。)

iOS12までは、AppDelegateでの対応、iOS13からはSceneDelegateで対応することになっていました。

Pure SwiftUIではView、App、Sceneのどこでも処理できるようになります。

Viewで処理する場合はこうなります。

struct SmallView: View {
    
    @Environment(\.scenePhase) private var scenePhase

    var body: some View {
        Text("Hello World")
            .onChange(of: scenePhase) { phase in
                print("user scene changed to..\(phase)")
            }
    }
}

App で処理する場合はこうなります。

@main
struct MyApp: App {
    @Environment(\.scenePhase) private var scenePhase

    var body: some Scene {
        WindowGroup {
            MyRootView()
        }
        .onChange(of: scenePhase) { phase in
            if phase == .background {
                print("changed to background!")
            }
        }
    }
}

ScenePhaseactiveinactivebackgroundの3値をとるenumです。

openURL

アプリ間連携や、その他細々したところでよく使うopenURLですが、こちらは今のところViewでしかハンドリングできないようです。

struct SmallView: View {
    var body: some View {
        Text("Hello")
            .onOpenURL{ url in
                print(" called with url.. \(url)")
            }

    }
}

アプリ起動画面

今までは、LaunchScreen Storyboardを使っていましたね。(古くはDefault.pngという固定イメージファイルを使っていましたが……。)

Pure Swift UIアプリでは、info.plistにファイル名を書くことでその画像を起動画面にすることができます。

 <key>UILaunchScreen</key>
    <dict>
        <key>UIImageName</key>
        <string>startScreen</string>
    </dict>

viewDidAppear系

今までViewControllerで使っていたviewDidAppear/viewDidDisapperはViewのonAppear、 onDisapperになります。

struct SmallView: View {
    
    var body: some View {
        Text("Hello World")
            .onAppear {
                print("use view appeared!")
                
            }
            .onDisappear {
                print("user view disappeared!")
            }

    }
}

まとめ

正直なところ、Pure SwiftUIは、公式ドキュメントの整備もまだまだなので、これからまだいろいろと変更がありそうです。

本番のコードにいれるのはまだ先かもしれませんが、プライベートプロジェクトなどで是非試してみましょう。