// -- Usage | |
struct Content: View { | |
@State var open = false | |
@State var popoverSize = CGSize(width: 300, height: 300) | |
var body: some View { | |
WithPopover( | |
showPopover: $open, | |
popoverSize: popoverSize, | |
content: { | |
Button(action: { self.open.toggle() }) { | |
Text("Tap me") | |
} | |
}, | |
popoverContent: { | |
VStack { | |
Button(action: { self.popoverSize = CGSize(width: 300, height: 600)}) { | |
Text("Increase size") | |
} | |
Button(action: { self.open = false}) { | |
Text("Close") | |
} | |
} | |
}) | |
} | |
} | |
// -- Source | |
struct WithPopover<Content: View, PopoverContent: View>: View { | |
@Binding var showPopover: Bool | |
var popoverSize: CGSize? = nil | |
let content: () -> Content | |
let popoverContent: () -> PopoverContent | |
var body: some View { | |
content() | |
.background( | |
Wrapper(showPopover: $showPopover, popoverSize: popoverSize, popoverContent: popoverContent) | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
) | |
} | |
struct Wrapper<PopoverContent: View> : UIViewControllerRepresentable { | |
@Binding var showPopover: Bool | |
let popoverSize: CGSize? | |
let popoverContent: () -> PopoverContent | |
func makeUIViewController(context: UIViewControllerRepresentableContext<Wrapper<PopoverContent>>) -> WrapperViewController<PopoverContent> { | |
return WrapperViewController( | |
popoverSize: popoverSize, | |
popoverContent: popoverContent) { | |
self.showPopover = false | |
} | |
} | |
func updateUIViewController(_ uiViewController: WrapperViewController<PopoverContent>, | |
context: UIViewControllerRepresentableContext<Wrapper<PopoverContent>>) { | |
uiViewController.updateSize(popoverSize) | |
if showPopover { | |
uiViewController.showPopover() | |
} | |
else { | |
uiViewController.hidePopover() | |
} | |
} | |
} | |
class WrapperViewController<PopoverContent: View>: UIViewController, UIPopoverPresentationControllerDelegate { | |
var popoverSize: CGSize? | |
let popoverContent: () -> PopoverContent | |
let onDismiss: () -> Void | |
var popoverVC: UIViewController? | |
required init?(coder: NSCoder) { fatalError("") } | |
init(popoverSize: CGSize?, | |
popoverContent: @escaping () -> PopoverContent, | |
onDismiss: @escaping() -> Void) { | |
self.popoverSize = popoverSize | |
self.popoverContent = popoverContent | |
self.onDismiss = onDismiss | |
super.init(nibName: nil, bundle: nil) | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
} | |
func showPopover() { | |
guard popoverVC == nil else { return } | |
let vc = UIHostingController(rootView: popoverContent()) | |
if let size = popoverSize { vc.preferredContentSize = size } | |
vc.modalPresentationStyle = UIModalPresentationStyle.popover | |
if let popover = vc.popoverPresentationController { | |
popover.sourceView = view | |
popover.delegate = self | |
} | |
popoverVC = vc | |
self.present(vc, animated: true, completion: nil) | |
} | |
func hidePopover() { | |
guard let vc = popoverVC, !vc.isBeingDismissed else { return } | |
vc.dismiss(animated: true, completion: nil) | |
popoverVC = nil | |
} | |
func presentationControllerWillDismiss(_ presentationController: UIPresentationController) { | |
popoverVC = nil | |
self.onDismiss() | |
} | |
func updateSize(_ size: CGSize?) { | |
self.popoverSize = size | |
if let vc = popoverVC, let size = size { | |
vc.preferredContentSize = size | |
} | |
} | |
} | |
} |
if you add the following func you'll get the popover on iPhone as well
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none // this is what forces popovers on iPhone
}
@wassupdoc Where should that func to be added ?
@ccwasden If the popovercontent is getting modiifed while using it, it doesn't reflect in the view
Ignore my above comment. I figured that out :)
Brilliant mate -- a real help to SwiftUI folks :-)
@dmallass - I'm having the same issue where the view isn't reflecting the underlying state changes. happen to remember what was causing that issue for you?
Yes, this didn't work on iPhone simulators on 15.2, but adding the function above (by @wassupdoc ) is what did it. Add it to the class, WrapperViewController.
@dmallass - I'm having the same issue where the view isn't reflecting the underlying state changes. happen to remember what was causing that issue for you?
I have solved this issue by adding the following code to func updateUIViewController
after the if else pair:
if let hostingController = uiViewController.popoverVC as? UIHostingController<PopoverContent> {
hostingController.rootView = popoverContent()
}
it's not working as popOver it's showing as sheet