I'm using the following pattern:
lazy var hostingVC = UIHostingController(rootView: EDWrapperView(exportDestinationVC: self))
override func viewDidLoad() {
super.viewDidLoad()
self.addChild(hostingVC)
self.view.addSubview(hostingVC.view)
hostingVC.view.translatesAutoresizingMaskIntoConstraints = false
hostingVC.view.backgroundColor = .systemGroupedBackground
self.view.topAnchor.constraint(equalTo: hostingVC.view.topAnchor).isActive = true
self.view.bottomAnchor.constraint(equalTo: hostingVC.view.bottomAnchor).isActive = true
self.view.leadingAnchor.constraint(equalTo: hostingVC.view.leadingAnchor).isActive = true
self.view.trailingAnchor.constraint(equalTo: hostingVC.view.trailingAnchor).isActive = true
configureNavigation()
// Theme
ThemeManager.shared.registerForThemeChanges(observer: self)
}
You can see that the SwiftUIView has a reference to the UIKitVC for calling functions (it kinda acts as a ViewModel).
In that very same UIViewController, I have a function that presents a popover, which requires a sourceView and a sourceRect.
func showActivityVC(sourceRect: CGRect) {
let activityVC = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
if let popOver = activityVC.popoverPresentationController {
popOver.sourceView = self.view! // Pass the UIVC view as sourceView
popOver.sourceRect = sourceRect // Source rect has been computed by GeometryReader in SwiftUI. But its not exactly at the right position
}
}
In the SwiftUIView, the button looks like this:
List {
// Export button to iOS Share sset
Section(header: Text("Export")) {
GeometryReader { geometry in
Button(action: {
let insideFrame = geometry.frame(in: .global) // Get rect in the global view, which should be the same as the UIViewController view
self.exportDestinationVC.presentIOSShareSheet(sourceRect: insideFrame)
}, label: {
HStack(alignment: .firstTextBaseline) {
Spacer()
Image(systemName: "square.and.arrow.up")
Text("Export")
Spacer()
}
})
.contentShape(Rectangle())
.position(x: geometry.size.width/2.0, y: geometry.size.height/2.0)
}
}
}
I use a GeometryReader
to get the frame of the button in the global view, which should be the same as the UIViewController view.
It works, but not exactly, the popover does not point exactly where it should, there are some inaccuracies that comes from I don't know where.
How can I get the exact sourceRect of the SwiftUI button inside the SwiftUI view?
Here is a simpler working solution, without needing a Coordinator. Meanwhile, it allows setting the title, font, and changing the color dynamically.
struct ButtonWithSourceView: UIViewRepresentable {
var title: String
var font: UIFont
@Binding var color: UIColor
var action: (UIButton) -> Void
func makeUIView(context: Self.Context) -> UIButton {
let uiButton = UIButton()
uiButton.setTitle(title, for: .normal)
uiButton.titleLabel?.font = font
uiButton.setTitleColor(color, for: .normal)
let uiAction = UIAction() { _ in
action(uiButton)
}
uiButton.addAction(uiAction, for: .touchUpInside)
return uiButton
}
func updateUIView(_ uiView: UIButton, context: Self.Context) {
uiView.setTitleColor(color, for: .normal)
}
}