🐥

UIHostingControllerのすすめ

2022/05/08に公開

UIHostingControllerのすすめ

Xcode-13.1

はじめに

SwiftUI を使ってみてレイアウトとかはすごく楽になった気がするのですが画面遷移でいろいろつまずくことがありました。

  • 2画面前に戻りたいとか
  • @Binding の値更新すると画面閉じちゃうとか
  • ...etc

画面遷移だけ UIKit でやるといい感じというのを前に聞いたので UIHostingController を使って実際にやってみました。

実装

View のデータ更新を HostingController からできるやつを作ります。

Storyboard を使ってるので init(coder:)View を生成しています。

import SwiftUI
import Combine

final class HogeHostingController: UIHostingController<HogeView> {

    private let viewModel = HogeViewModel()
    private var subscriptions = Set<AnyCancellable>()

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder, rootView: .init(viewModel: viewModel))
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.text = "ほげ"
        viewModel.subject.sink {
            print("ボタン押下")
        }.store(in: &subscriptions)
    }
}

final class HogeViewModel: ObservableObject {

    var text: String = ""
    let buttonTitle = "ぼたん"
    let subject = PassthroughSubject<Void, Never>()
}

struct HogeView: View {

    @ObservedObject var viewModel: HogeViewModel

    var body: some View {
        VStack {
            Text(viewModel.text)
            Button(viewModel.buttonTitle) {
                viewModel.subject.send()
            }
        }
    }
}

上記を実行すると無事、画面に「ほげ」と表示されます👏

HostingControllerViewViewModel を共有しており、 ViewModel の値を更新すれば View も更新されるようになっています。ボタン押下などのイベントは sinkHostingController で処理できるようになっています。

失敗例

HostingController から View を更新するためにいろいろ試してみましたが下記実装では無理でした。

Stateを使うその1

以下のようにすると値は更新されず画面には「ほげ」と表示されます。

import SwiftUI

final class HogeHostingController: UIHostingController<HogeView> {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder, rootView: .init())
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        rootView.text = "ふが"
    }
}

struct HogeView: View {

    @State var text = "ほげ"

    var body: some View {
        Text(text)
    }
}

遅延実行とか viewDidAppear で更新とかもダメでした。View 側に update メッソッドを作りその中で @State を更新するという方法もダメでした。

Stateを使うその2

HostingController@State を持たして View 側に @Binding で渡せばいけるか?と思いましたがダメでした。

無理やりやると実行時にこういう警告が出ます。

[SwiftUI] Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.

おわりに

ObservableObject を使うことで無事 HostingController から View を更新できました🎉

Discussion