Greetings, traveler!
When working with SwiftUI, you often need to bridge UIKit and SwiftUI components. One common requirement is to access the underlying UIHostingController from within a SwiftUI view. This can be useful when you need to perform UIKit-specific actions such as navigation, presenting alerts, or interacting with UIKit lifecycle events.
In this article, we’ll explore a clean way to expose a UIHostingController to SwiftUI views.
Introducing HostingControllerProvider
To enable SwiftUI views to access their hosting controller, we define a provider class marked with @Observable. This class will store a weak reference to the controller:
@Observable
final class HostingControllerProvider {
weak var viewController: UIViewController?
}Injecting the Provider
We can create the provider and inject it into the SwiftUI view’s environment before wrapping the view into a UIHostingController. Once the controller is initialized, we assign it back to the provider:
class Router {
var navigationController: UINavigationController?
func show() {
let provider = HostingControllerProvider()
let view = SomeView().environment(provider)
let controller = UIHostingController(rootView: view)
provider.viewController = controller
navigationController?.pushViewController(controller, animated: true)
}
}Now, any SwiftUI view within this hierarchy can access its UIHostingController through the HostingControllerProvider.
struct SomeView: View {
@Environment(HostingControllerProvider.self) private var hostingProvider
var body: some View {
Text("Hello!")
.onAppear {
hostingProvider.viewController?.navigationItem.title = "Some Screen"
}
}
}Adding Convenience with View Extension
To streamline this pattern, we can add an extension to View that packages the provider setup and controller initialization:
extension View {
func hosted() -> UIHostingController<some View> {
let provider = HostingControllerProvider()
let view = environment(provider)
let controller = UIHostingController(rootView: view)
provider.viewController = controller
return controller
}
}This lets you easily wrap any SwiftUI view and retrieve its hosting controller:
class Router {
var navigationController: UINavigationController?
func show() {
navigationController?.pushViewController(SomeView().hosted(), animated: true)
}
}Summary
This approach allows SwiftUI views to interact with their hosting controllers without tightly coupling them to UIKit logic.
