SwiftUIからUIViewController
やUIView
を表示する際は、UIViewControllerRepresentable
やUIViewRepresentable
を使用することで表示が出来ますが、UIViewController
からSwiftUIを表示する方法を知りたかったので調べました。
環境
- Xcode 13.3
UIHositngViewControllerを使用する
SwiftUIのView
をUIViewController
として使用する為には、UIHostingViewController
を使用します。
UIHostingViewController
はSwiftUIのView
階層を管理するUIViewController
です。
SwiftUI ViewをUIKit View階層に統合する場合、UIHostingControllerオブジェクトを作成します。 作成時に、このViewControllerのroot viewとして使用するSwiftUI Viewを指定します。 rootViewのプロパティを使用して、後でそのビューを変更できます。HostingControllerを他のViewControllerと同じように使用するには、インターフェイスにchild ViewControllerとして表示するか、埋め込みます。
使用例
SwiftUIView
例として、下記のSwiftUIのView
を作成しました。このView
をUIViewController
から表示します。
import SwiftUI
struct SwiftUIView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
ZStack {
Rectangle()
.fill(.yellow)
.ignoresSafeArea()
VStack(spacing: 16) {
Text("SwiftUI View")
Button {
dismiss()
} label: {
Text("画面を閉じる")
}
}
}
}
}
ViewControllerから表示する
UIHostingViewController(rootView:)
にSwiftUIView
を指定してインスタンス化したものをpresent(_:, animated:)
メソッドを使用するだけのシンプルなものになります。
import UIKit
import SwiftUI
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func presentSwiftUIView() {
let controller = UIHostingController(rootView: SwiftUIView())
controller.modalPresentationStyle = .overFullScreen
present(controller, animated: true)
}
}
デモ
このように表示することが出来ました。
UIKitライフサイクルを使用する
上記までは、表示前にUIHositingController(rootView:)
を使用して呼び出すケースでしたが、UIHositingController
クラスを用意することでライフサイクルを使用することも出来ます。
ViewModelを用意する
今回はviewDidLoad
でSwiftUIのView
の背景色の変更を試してみたいと思います。値の変更を反映する為に、@ObsrvableObject
のSwiftUIViewModel
を用意しました。
import SwiftUI
import Combine
class SwiftUIViewModel: ObservableObject {
@Published var backgroundColor: Color = .yellow
let subject = PassthroughSubject<Void, Never>()
}
背景色をパブリッシュする為のbackgroundColor
と、ボタンの押下イベントを発行する為のsubject
を宣言しています。
SwiftUIView
viewModel
の定義をしている為、それに伴い、Rectangle().fill()
の箇所と、Button
の箇所を変更しています。
import SwiftUI
import Combine
struct SwiftUIView: View {
@ObservedObject var viewModel: SwiftUIViewModel
var body: some View {
ZStack {
Rectangle()
.fill(viewModel.backgroundColor)
.ignoresSafeArea()
VStack(spacing: 16) {
Text("SwiftUI View")
Button {
viewModel.subject.send()
} label: {
Text("画面を閉じる")
}
}
}
}
}
SwiftUIViewHostingController
UIHostingController
をジェネリック型にする際には、ContentとなるSwiftUIのView
の記述が必要です。記載していない場合は、Xcodeが教えてくれます。
import SwiftUI
import Combine
class SwiftUIViewHostingController: UIHostingController<SwiftUIView> {
private let viewModel: SwiftUIViewModel
private var anyCancellable: AnyCancellable?
init(viewModel: SwiftUIViewModel) {
self.viewModel = viewModel
super.init(rootView: SwiftUIView(viewModel: viewModel))
}
required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
viewModel.backgroundColor = .red
anyCancellable = viewModel.subject.sink { [weak self] _ in
self?.dismiss(animated: true)
}
}
}
プロパティ
private let viewModel: SwiftUIViewModel
private var anyCancellable: AnyCancellable?
viewModel
の値を変更してSwiftUIView
の見た目を変更する為に、SwiftUIViewModel
を定義しています。また、ボタンの押下イベントを受け取るので、anyCancellable
も用意しています。
init
引数として渡されたviewModel
を自身のviewModel
とSwiftUIView
のviewModel
に渡してUIHostingController
を初期化しています。
init(viewModel: SwiftUIViewModel) {
self.viewModel = viewModel
super.init(rootView: SwiftUIView(viewModel: viewModel))
}
viewDidLoad
viewDidLoad
時に、背景色を.red
に変更してみました。そして、ボタンの押下された際には、dismiss(animated:)
メソッドを呼び出して画面を破棄するようにしています。
override func viewDidLoad() {
super.viewDidLoad()
viewModel.backgroundColor = .red
anyCancellable = viewModel.subject.sink { [weak self] _ in
self?.dismiss(animated: true)
}
}
ViewControllerから表示する
ViewController
でpresentSwiftUIView()
を実行した際に上記で作成したSwiftUIViewHostingController
を渡すように変更しました。
import UIKit
import SwiftUI
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func presentSwiftUIView() {
let controller = SwiftUIViewHostingController(viewModel: SwiftUIViewModel())
controller.modalPresentationStyle = .overFullScreen
present(controller, animated: true)
}
}
デモ
無事に背景色が赤色に変更され、ボタン押下で画面を閉じることが出来ました。
おわりに
0からSwiftUIにリプレイスをすることは大変ですが、このように一部をSwiftUI
に変更することは簡単に出来ることが判明した為、少しずつSwiftUI
に変更していくのも可能だなと感じました。
WWDC22: Use SwiftUI with UIKitのセッションで説明があるのですが、iOS 16からUIHostingConfiguration
を使用して、カスタムUICollectionView
やUITableView
セルをシームレスに構築出来るようになるみたいです。楽しみですね!