In SwiftUI, how to use UIHostingController inside an UIView or as an UIView? In SwiftUI, how to use UIHostingController inside an UIView or as an UIView? ios ios

In SwiftUI, how to use UIHostingController inside an UIView or as an UIView?


View controllers are not just for the top level scene. We often place view controllers within view controllers. It’s called “view controller containment” and/or “child view controllers”. (BTW, view controller containers are, in general, a great way to fight view controller bloat in traditional UIKit apps, breaking complicated scenes into multiple view controllers.)

So,

  • Go ahead and use UIHostingController:

    let controller = UIHostingController(rootView: ...)

    and;

  • Add the view controller can then add the hosting controller as a child view controller:

    addChild(controller)view.addSubview(controller.view)controller.didMove(toParent: self)

    Obviously, you’d also set the frame or the layout constraints for the hosting controller’s view.

    See the Implementing a Container View Controller section of the UIViewController documentation for general information about embedding one view controller within another.


For example, let’s imagine that we had a SwiftUI View to render a circle with text in it:

struct CircleView : View {    @ObservedObject var model: CircleModel    var body: some View {        ZStack {            Circle()                .fill(Color.blue)            Text(model.text)                .foregroundColor(Color.white)        }    }}

And let’s say this was our view’s model:

import Combineclass CircleModel: ObservableObject {    @Published var text: String    init(text: String) {        self.text = text    }}

Then our UIKit view controller could add the SwiftUI view, set its frame/constraints within the UIView, and update its model as you see fit:

import UIKitimport SwiftUIclass ViewController: UIViewController {    private weak var timer: Timer?    private var model = CircleModel(text: "")    override func viewDidLoad() {        super.viewDidLoad()        addCircleView()        startTimer()    }    deinit {        timer?.invalidate()    }}private extension ViewController {    func addCircleView() {        let circleView = CircleView(model: model)        let controller = UIHostingController(rootView: circleView)        addChild(controller)        controller.view.translatesAutoresizingMaskIntoConstraints = false        view.addSubview(controller.view)        controller.didMove(toParent: self)        NSLayoutConstraint.activate([            controller.view.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),            controller.view.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),            controller.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),            controller.view.centerYAnchor.constraint(equalTo: view.centerYAnchor)        ])    }    func startTimer() {        var index = 0        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in            index += 1            self?.model.text = "Tick \(index)"        }    }}


I have some idea in mind.

  1. Wrap the SwiftUI with a UIHostingController
  2. Initialize the controller
  3. Add the new controller as a child view controller
  4. Add the controller view as a subview to where it should go

Thus:

addChild(hostingViewController)hostingViewController.view.frame = ...view.addSubview(hostingViewController.view)hostingViewController.didMove(toParent: self)

A view controller always uses other view controllers as views.

Stanford CS193P, https://youtu.be/w7a79cx3UaY?t=679

Reference