diff --git a/Sources/Playbook/Components/Popover/PopoverCatalog.swift b/Sources/Playbook/Components/Popover/PopoverCatalog.swift index 284214b8c..2eade5316 100644 --- a/Sources/Playbook/Components/Popover/PopoverCatalog.swift +++ b/Sources/Playbook/Components/Popover/PopoverCatalog.swift @@ -16,6 +16,7 @@ public struct PopoverCatalog: View { @State private var isPresented4: Bool = false @State private var isPresented5: Bool = false @State private var isPresented6: Bool = false + @State private var isPresented7: Bool = false public init() {} @@ -25,7 +26,12 @@ public struct PopoverCatalog: View { PBDoc(title: "Dropdrown") { dropdownPopover } PBDoc(title: "Scroll") { scrollPopover } PBDoc(title: "Close options") { onClosePopover } - .edgesIgnoringSafeArea(.all) + PBDoc(title: "Explorarion Popover") { explorationPopover } + .padding(.bottom, 500) + .edgesIgnoringSafeArea(.all) + } + .onTapGesture { + isPresented7 = false } .popoverHandler(id: 1) .popoverHandler(id: 2) @@ -167,4 +173,34 @@ public struct PopoverCatalog: View { .frame(width: 200, height: 150) } } + + @State private var viewFrame: CGRect = CGRect(x: 200, y: 400, width: 0, height: 0) + private var explorationPopover: some View { + HStack { + Text("This is an exploration Popover") + .pbFont(.body, color: .text(.default)) + .frameReader { viewFrame = $0 } + PBButton( + variant: .secondary, + shape: .circle, + icon: .fontAwesome(.info) + ) { + isPresented7.toggle() + } + .handlerPopoverController( + isPresented: $isPresented7, + position: CGPoint(x: viewFrame.midX, y: viewFrame.maxY) + ) { + Text("I'm a popover. I can show content of any size.") + .pbFont(.body, color: .text(.default)) + } + } + .onTapGesture { + isPresented7 = false + } + } +} + +#Preview { + PopoverCatalog() } diff --git a/Sources/Playbook/Components/Popover/PopoverExploration.swift b/Sources/Playbook/Components/Popover/PopoverExploration.swift new file mode 100644 index 000000000..69184116b --- /dev/null +++ b/Sources/Playbook/Components/Popover/PopoverExploration.swift @@ -0,0 +1,176 @@ +// +// Playbook Swift Design System +// +// Copyright © 2024 Power Home Remodeling Group +// This software is distributed under the ISC License +// +// PopoverExploration.swift +// + +import SwiftUI + +struct PopView: View { + @Binding var isPresented: Bool + var body: some View { + PBCard( + border: false, + padding: Spacing.small, + shadow: .deeper, + width: nil) { + Text("I'm a popover. I can show content of any size.") + .pbFont(.body, color: .text(.default)) + } + .onTapGesture { + isPresented = false + } + } +} + +extension View { + func handlerPopoverController(isPresented: Binding, position: CGPoint, @ViewBuilder content: @escaping (() -> Content)) -> some View { +// #if os(macOS) +// self.background(PopHostingView(isPresented: isPresented, content: content)) +// #elseif os(iOS) + self.background(PopHostingView(isPresented: isPresented, position: position, content: content)) +// #endif + } +} + +#if os(macOS) +import AppKit + +struct PopHostingView: NSViewRepresentable { + @Binding var isPresented: Bool + let position: CGPoint + let content: () -> Content + + func makeNSView(context: Context) -> NSView { + let nsView = NSView(frame: .zero) + return nsView + } + + func updateNSView(_ nsView: NSView, context: Context) { + if isPresented { + context.coordinator.showPopover(from: nsView) + } else { + context.coordinator.dismissPopover() + } + } + + func makeCoordinator() -> Coordinator { + Coordinator(isPresented: $isPresented, content: content()) + } + + class Coordinator: NSObject { + let popover: NSPopover + let contentController: NSHostingController + @Binding var isPresented: Bool + + init(isPresented: Binding, content: Content) { + self.popover = NSPopover() + self.contentController = NSHostingController(rootView: content) + self._isPresented = isPresented + + super.init() + self.popover.contentViewController = contentController + self.popover.behavior = .transient + } + + func showPopover(from sender: NSView) { + var positioningView: NSView? + positioningView = NSView() + positioningView?.frame = sender.frame + if let view = positioningView { + sender.superview?.addSubview(view, positioned: .below, relativeTo: sender) + } + + popover.contentViewController = PopViewController() + popover.behavior = .transient + popover.show(relativeTo: .zero, of: positioningView!, preferredEdge: .minY) + + positioningView?.frame = NSMakeRect(0, 200, 0, 0) + } + + func dismissPopover() { + popover.performClose(nil) + } + } + + class PopViewController: NSViewController { + override func loadView() { + let hostingView = NSHostingView(rootView: PopView(isPresented: .constant(false))) + hostingView.layoutSubtreeIfNeeded() + let contentSize = hostingView.fittingSize + let popoverView = NSView(frame: CGRect(origin: .zero, size: contentSize)) + hostingView.frame = popoverView.bounds + popoverView.addSubview(hostingView) + self.view = popoverView + } + } +} +#endif + +#if os(iOS) +import UIKit + +struct PopHostingView: UIViewRepresentable { + @Binding var isPresented: Bool + let position: CGPoint + let content: () -> Content + + func makeUIView(context: Context) -> UIView { + let uiView = UIView(frame: .zero) + return uiView + } + + func updateUIView(_ uiView: UIView, context: Context) { + if isPresented { + context.coordinator.showPopover(from: uiView) + } else { + context.coordinator.dismissPopover() + } + } + + func makeCoordinator() -> Coordinator { + Coordinator(isPresented: $isPresented, position: position, content: content()) + } + + class Coordinator: NSObject, UIPopoverPresentationControllerDelegate { + private let contentViewController: UIHostingController + private weak var anchorView: UIView? + @Binding var isPresented: Bool + private let position: CGPoint + + var popoverView: UIView? + + init(isPresented: Binding, position: CGPoint, content: Content) { + self.contentViewController = UIHostingController(rootView: content) + self._isPresented = isPresented + self.position = position + super.init() + + let hostingView = UIHostingController(rootView: PopView(isPresented: $isPresented)) + let popoverView = hostingView.view + popoverView?.frame = CGRect(x: position.x, y: position.y, width: 200, height: 100) + popoverView?.layer.cornerRadius = 10 + popoverView?.backgroundColor = .clear + self.popoverView = popoverView + } + + func showPopover(from anchorView: UIView) { + guard let rootVC = anchorView.window?.rootViewController else { return } + + rootVC.modalPresentationStyle = .popover + if let popoverView = popoverView { + rootVC.view.addSubview(popoverView) + } + } + + func dismissPopover() { + if let popoverView = popoverView { + popoverView.removeFromSuperview() + } + } + } +} +#endif