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’sview
.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)"
}
}
}
SwiftUI - How to access UIHostingController from SwiftUI
Here is a demo of possible approach - to use external configuration wrapper class to hold weak link to controller and inject it into SwiftUI view (as alternate it is also possible to make it ObservableObject
to combine with other global properties and logic).
Tested with Xcode 12.5 / iOS 14.5
class Configuration {
weak var hostingController: UIViewController? // << wraps reference
}
struct SwiftUIView: View {
let config: Configuration // << reference here
var body: some View {
Button("Demo") {
self.config.hostingController?.title = "New Title"
}
}
}
let configuration = ExtConfiguration()
let controller = UIHostingController(rootView: SwiftUIView(config: configuration))
// injects here, because `configuration` is a reference !!
configuration.hostingController = controller
controller.title = "title"
MyNavigationManager.present(controller)
Include SwiftUI views in existing UIKit application
edit 05/06/19: Added information about UIHostingController as suggested by @Departamento B in his answer. Credits go to him!
Using SwiftUI within UIKit
One can use SwiftUI
components in existing UIKit
environments by wrapping a SwiftUI
View
into a UIHostingController
like this:
let swiftUIView = SomeSwiftUIView() // swiftUIView is View
let viewCtrl = UIHostingController(rootView: swiftUIView)
It's also possible to override UIHostingController
and customize it to one's needs, e. g. by setting the preferredStatusBarStyle
manually if it doesn't work via SwiftUI
as expected.
UIHostingController
is documented here.
Using UIKit within SwiftUI
If an existing UIKit
view should be used in a SwiftUI
environment, the UIViewRepresentable
protocol is there to help! It is documented here and can be seen in action in this official Apple tutorial.
Compatibility
Please note that you cannot use SwiftUI
on iOS versions < iOS 13, as SwiftUI
is only available on iOS 13 and above. See this post for more information.
If you want to use SwiftUI
in a project with a target below iOS 13, you need to tag your SwiftUI
structs with @available(iOS 13.0.0, *)
attribute.
Related Topics
Uirefreshcontrol with Uicollectionview in iOS7
How to Listen for All Notifications Sent to the iOS Nsnotificationcenter's Defaultcenter
Hide Keyboard When Scroll Uitableview
Bundle.Main.Path(Forresource:Oftype:Indirectory:) Returns Nil
Letter by Letter Animation for Uilabel
Cocoapods Could Not Find Compatible Versions for Pod "Firebase/Core" | Cloud_Firestore, Flutter
Connect Outlet of a Cell Prototype in a Storyboard
How to Remove the Authorization Prompt from Command-Line Instances of Instruments (Xcode)
Pods-Resources.Sh Permission Denied in iOS Project
How to Develop iPhone Mdm Server
Dial a Phone Number with an Access Code Programmatically in iOS
Modify Uiimage Renderingmode from a Storyboard/Xib File
How to Dismiss the iOS Keyboard
iOS Horizontal Slideview with Vertical Menu
Library Not Found for -Ldoubleconversion