Skip to content

Instantly share code, notes, and snippets.

@wassupdoc
Forked from ccwasden/WithPopover.swift
Last active February 20, 2024 08:48
Show Gist options
  • Save wassupdoc/7c47efa3fe480fa9a70a95ac89448bde to your computer and use it in GitHub Desktop.
Save wassupdoc/7c47efa3fe480fa9a70a95ac89448bde to your computer and use it in GitHub Desktop.
Custom size popover in iOS SwiftUI
// -- 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
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none // this is what forces popovers on iPhone
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment