I created UIViewControllerRepresentable to present popover.
I did test it on Xcode 13 and iOS 15 it working great as video on this link
But it doesn't work as expected on Xcode 12.5.1 and iOS 14.8 as video on this link
I don't know why it doesn't work on iOS 14.8 , is there other solution to make it work on both iOS versions ?
My code :
struct PopoverViewModifier<PopoverContent>: ViewModifier where PopoverContent: View {
@Binding var isPresented: Bool
let onDismiss: (() -> Void)?
let content: () -> PopoverContent
func body(content: Content) -> some View {
content
.background(
Popover(
isPresented: self.$isPresented,
onDismiss: self.onDismiss,
content: self.content
)
)
}
}
extension View {
func cocoaPopover<Content>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
content: @escaping () -> Content
) -> some View where Content: View {
ModifiedContent(
content: self,
modifier: PopoverViewModifier(
isPresented: isPresented,
onDismiss: onDismiss,
content: content
)
)
}
}
struct Popover<Content: View> : UIViewControllerRepresentable {
@Binding var isPresented: Bool
let onDismiss: (() -> Void)?
@ViewBuilder let content: () -> Content
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self, content: self.content())
}
func makeUIViewController(context: Context) -> UIViewController {
return UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
let host = context.coordinator.host
host.rootView = content()
if host.viewIfLoaded?.window == nil && self.isPresented {
host.preferredContentSize = host.sizeThatFits(in: CGSize(width: Int.max , height: Int.max))
host.modalPresentationStyle = UIModalPresentationStyle.popover
host.popoverPresentationController?.delegate = context.coordinator
host.popoverPresentationController?.sourceView = uiViewController.view
host.popoverPresentationController?.sourceRect = uiViewController.view.bounds
uiViewController.present(host, animated: true, completion: nil)
} else if self.isPresented == false {
host.dismiss(animated: false, completion: nil)
}
}
class Coordinator: NSObject, UIPopoverPresentationControllerDelegate {
let host: UIHostingController<Content>
private let parent: Popover
init(parent: Popover, content: Content) {
self.parent = parent
self.host = UIHostingController(rootView: content)
}
func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
self.parent.isPresented = false
if let onDismiss = self.parent.onDismiss {
onDismiss()
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
}
Your issue has nothing to do with the Representable. It seems there may be a bug in iOS14 and the way it handles buttons on a Toolbar. When you place a button inside a Toolbar, its geometry behavior is a little strange (on iOS14). Run the following code and check the results on both iOS versions.
struct ContentView: View {
@State var show = false
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
}
.toolbar {
Button("Show") {
show = true
}
.border(Color.green)
}
}
}
}
On iOS 15, it will look like this:
but on iOS 14, it looks like this:
So what happened to the green border? Anything placed in a border, overlay or background gets drawn somewhere else (and for what I've seen so far, out of the screen).
Note that this problem only occurs for buttons inside a toolbar.
This bad geometry has a direct effect on your popover. So if we find a way to fix that geometry, the problem for your popover will go away too. I can think of two workarounds:
Text("Show").onTapGesture { ... }
This works, but if you want to emulate the look of a real button, you'll need to put some more work. Fortunately, there's workaround number 2.
struct ContentViewx: View {
@State var show = false
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
}
.toolbar {
Text("Show")
.opacity(0)
.overlay(
Button("Show") {
show = true
}
)
.cocoaPopover(isPresented: $show) {
print("dismiss!")
} content: {
Text("Hello, this is the popover!")
}
}
}
}
}