iOSやmacOSのバージョンが上がり、SwiftUIの機能も増えてきました。SwiftUIで作れる部分はSwiftUIを使い、UIKitと併用するということも徐々に出てきているのではないでしょうか?
この記事では、アプリのライフサイクルをUIKitからSwiftUIに変更する方法について解説します。
ビューを実装する
移行前のアプリは、Main.storyboard
ファイルがロードされて、ViewController
クラスのビューが表示されるという構造になっています。このビューを表示する、SwiftUIのビューを実装します。
ContentView.swift
を追加し、次のコードを入力します。
import SwiftUI
struct ContentView: UIViewControllerRepresentable {
typealias UIViewControllerType = ViewController
func makeUIViewController(context: Context) -> ViewController {
// Main.storyboardをロードする
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// ViewControllerをインスタンス化
guard let viewController = storyboard.instantiateInitialViewController() as? ViewController else {
fatalError("Couldn't instanciate a ViewController class.")
}
return viewController
}
func updateUIViewController(_ uiViewController: ViewController, context: Context) {
}
}
UIKitのビューコントローラをSwiftUIのビューとして表示するには、UIViewControllerRepresentable
を使います。ViewController
を表示したいので、ViewController
を表示するUIViewControllerRepresentable
を継承したビューを実装しています。
この後の作業で、Main.storyboard
はロードされないように変更するので、ContentView.makeUIViewController()
メソッドで手動でロードしています。
SwiftUIのAppの適合タイプの追加
SwiftUIのApp
の適合タイプを追加します。ここではSampleApp
という名前にすることにしたので、SampleApp.swift
を追加し、次のようなコードを入力します。
// SampleApp.swift
import SwiftUI
@main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
エントリーポイントの移行
アプリのエントリーポイントを、AppDelegate (UIApplicationDelegateの適合クラス)
からSwiftUIのApp
に移行します。
まず、AppDelegate
の@main
を削除します。この記事では変更前の状態が分かるようにコメントアウトにしていますが、削除でOKです。
// AppDelegate.swift
import UIKit
// 削除する
//@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// ... 省略
}
Info.plistの編集
Info.plist
を編集して、アプリ起動時にMain.storyboard
を自動的に読み込まないように変更します。次のように操作します。
(1) ターゲットの設定を開き、「Info」タブを表示します。
(2) 「Main storyboard file base name」と「Principal class」(もし有ったら)を削除します。
(3) 「Application Scene Manifest」内の「Scene Configuration」を削除します。
AppDelegateのインスタンス化
AppDelegate
でしか実装できない処理もあるので、AppDelegate
が適切にSwiftUIのライフサイクルでも使われるようにします。それには、UIApplicationDelegateAdaptor
を使用します。SampleApp.swift
に次のようにコードを追加します。
// SampleApp.swift
import SwiftUI
@main
struct SampleApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
AppDelegate
クラスのインスタンス化はSwiftUIによって行われます。インスタンスはEnvironmentObject
経由で取得できます。そのためには、AppDelegate
クラスがObservableObject
プロトコルに適合する必要があります。次のように、AppDelegate.swift
にコードを追加します。
// AppDelegate.swift
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate, ObservableObject {
// ... 省略
}
シーンの処理を変更
Info.plist
の「Scene Configuration」を削除したので、AppDelegate
の対応する部分の処理を変更します。次の2つのメソッドを削除します。アプリの都合で必要という場合には、動作を確認して残してください。
application(_:configurationForConnecting:options:)
application(_:didDiscardSceneSessions:)
動作テスト
動作テストを行います。成功すると、Main.storyboard
内のViewController
のシーンが表示されます。