An alternative question title could be "How to add an UIHostingController's view as subview for an UIView?".
I am creating a new piece of UI component and would love to give SwiftUI a try. The image below is the current view structure. The UIView is what I am using right now (top right), and SwiftUI view is what I try to use (bottom right).
After I watched all SwiftUI videos from WWDC 2019. I still have no clue on how can I use a SwiftUI view and put it at where a UIView instance should go.
I noticed from "Integrating SwiftUI" talk is that there is an NSHostingView
for macOS, https://developer.apple.com/documentation/swiftui/nshostingview# which made me wonder if there is something similar to it or what is the alternative to achieve it.
I read questions like Include SwiftUI views in existing UIKit application mentioned that SwiftUI and UIKit can play together with UIHostingController
. However, what I am trying to do is to only adopt one small piece of SwiftUI and put it inside of my existing UIKit view component, not use it as a controller.
I am new to iOS development, please leave a comment if there is a way I can use view controller as UIView view. Thank you.
SwiftUI works seamlessly with the existing UI frameworks on all Apple platforms. For example, you can place UIKit views and view controllers inside SwiftUI views, and vice versa.
present the gray UIView like you would usually present a ViewController (appearing bottom up and user can slide down to dismiss). The bottomBar is a ContainerView and should not change by switching between the VC's, only the gray UIView which you can see in the 2nd picture.
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 Combine class 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 UIKit import SwiftUI class 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.
UIHostingController
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With