Reputation: 143
I have SwiftUI page, it's being navigated from UIKit view. I want to set a title to this page, what I'm doing is
// code of UIKit view
let controller = UIHostingController(rootView: SwiftUIView())
controller.title = "title"
MyNavigationManager.present(controller)
Is there a way I can access the hosting controller within SwiftUI?
Then I could write code like
self.hostingController?.title = "title"
Upvotes: 12
Views: 12730
Reputation: 10426
I went for a different option - subclass NSHostingController so that it provides itself as an environment variable.
struct SwiftUIView: View {
@EnvironmentObject var host:HSHostWrapper
var body: some View {
Button("Dismiss") {
host.controller?.dismiss(self)
}
}
}
let controller = HSHostingController(rootView: SwiftUIView())
this is achieved with the following HSHostingController (which is available in the HSSwiftUI package)
import Foundation
import SwiftUI
#if os(iOS) || os(tvOS)
import UIKit
public typealias ViewController = UIViewController
public typealias HostingController = UIHostingController
#elseif os(OSX)
import AppKit
public typealias ViewController = NSViewController
public typealias HostingController = NSHostingController
#endif
public class HSHostWrapper:ObservableObject {
public weak var controller:ViewController?
}
/// allows root view (and children) to access the hosting controller by adding
/// @EnvironmentObject var host:HSHostWrapper
/// then e.g. host.controller?.dismiss()
public class HSHostingController<Content>:HostingController<ModifiedContent<Content,SwiftUI._EnvironmentKeyWritingModifier<HSHostWrapper?>>> where Content : View {
public init(rootView:Content) {
let container = HSHostWrapper()
let modified = rootView.environmentObject(container) as! ModifiedContent<Content, _EnvironmentKeyWritingModifier<HSHostWrapper?>>
super.init(rootView: modified)
container.controller = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
edit: Note - this approach is now deprecated in HSSwiftUI. See my subsequent answer for a newer cleaner approach.
Upvotes: 7
Reputation: 10426
I have a new solution.
This relies on an extension to ViewController that provides itself as the input to a ViewBuilder.
I think it is very clean
let controller = ViewController() {
vc in
vc.title = "title"
return SwiftUIView()
}
MyNavigationManager.present(controller)
this relies on the HSSwiftUI package
https://swiftpackageindex.com/ConfusedVorlon/HSSwiftUI
Upvotes: 3
Reputation: 257663
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)
Upvotes: 16