From 53fb68468d66bfe4044f293c1631a2f7f6440dd6 Mon Sep 17 00:00:00 2001 From: "isis.silva" Date: Thu, 21 Nov 2024 16:56:14 -0500 Subject: [PATCH 1/4] add popover handler --- .../Components/Popover/PopoverCatalog.swift | 28 ++- .../Popover/PopoverExploration.swift | 171 ++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 Sources/Playbook/Components/Popover/PopoverExploration.swift diff --git a/Sources/Playbook/Components/Popover/PopoverCatalog.swift b/Sources/Playbook/Components/Popover/PopoverCatalog.swift index 284214b8c..6743480ac 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,9 @@ 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) } .popoverHandler(id: 1) .popoverHandler(id: 2) @@ -167,4 +170,27 @@ public struct PopoverCatalog: View { .frame(width: 200, height: 150) } } + + @State private var viewFrame: CGRect = CGRect(x: 200, y: 200, 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: $viewFrame.origin + ) { + Text("I'm a popover. I can show content of any size.") + .pbFont(.body, color: .text(.default)) + } + } + } } diff --git a/Sources/Playbook/Components/Popover/PopoverExploration.swift b/Sources/Playbook/Components/Popover/PopoverExploration.swift new file mode 100644 index 000000000..81a0a7ea7 --- /dev/null +++ b/Sources/Playbook/Components/Popover/PopoverExploration.swift @@ -0,0 +1,171 @@ +// +// 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 { + 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)) + } + + } +} + +extension View { + func handlerPopoverController(isPresented: Binding, position: Binding, @ViewBuilder content: @escaping (() -> Content)) -> some View { + self.background(PopHostingView(isPresented: isPresented, position: position, content: content)) + } +} + +#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()) + 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 + @Binding var 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() + } + + context.coordinator.position = position + } + + 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 + @Binding var position: CGPoint + + var popoverView: UIView? + + init(isPresented: Binding, position: Binding, content: Content) { + self.contentViewController = UIHostingController(rootView: content) + self._isPresented = isPresented + self._position = position + super.init() + + let hostingView = UIHostingController(rootView: PopView()) + let popoverView = hostingView.view + popoverView?.frame = CGRect(x: position.wrappedValue.x, y: position.wrappedValue.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 From 9fea6d8d46bccbdcf5e45477e22275d8d567ad0c Mon Sep 17 00:00:00 2001 From: "isis.silva" Date: Thu, 21 Nov 2024 17:19:40 -0500 Subject: [PATCH 2/4] fix macOS prop --- .../Playbook/Components/Popover/PopoverExploration.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Sources/Playbook/Components/Popover/PopoverExploration.swift b/Sources/Playbook/Components/Popover/PopoverExploration.swift index 81a0a7ea7..cbaa023c4 100644 --- a/Sources/Playbook/Components/Popover/PopoverExploration.swift +++ b/Sources/Playbook/Components/Popover/PopoverExploration.swift @@ -19,7 +19,6 @@ struct PopView: View { Text("I'm a popover. I can show content of any size.") .pbFont(.body, color: .text(.default)) } - } } @@ -34,7 +33,7 @@ import AppKit struct PopHostingView: NSViewRepresentable { @Binding var isPresented: Bool - let position: CGPoint + @Binding var position: CGPoint let content: () -> Content func makeNSView(context: Context) -> NSView { @@ -122,8 +121,6 @@ struct PopHostingView: UIViewRepresentable { } else { context.coordinator.dismissPopover() } - - context.coordinator.position = position } func makeCoordinator() -> Coordinator { @@ -146,7 +143,7 @@ struct PopHostingView: UIViewRepresentable { let hostingView = UIHostingController(rootView: PopView()) let popoverView = hostingView.view - popoverView?.frame = CGRect(x: position.wrappedValue.x, y: position.wrappedValue.y, width: 200, height: 100) + popoverView?.frame = CGRect(x: position.wrappedValue.x, y: position.wrappedValue.y, width: 500, height: 500) popoverView?.layer.cornerRadius = 10 popoverView?.backgroundColor = .clear self.popoverView = popoverView From 65fccc2b90fa425a8b486870760ce8087197fce4 Mon Sep 17 00:00:00 2001 From: "isis.silva" Date: Thu, 21 Nov 2024 17:23:37 -0500 Subject: [PATCH 3/4] fix macos position --- .../Components/Popover/PopoverCatalog.swift | 4 ++-- .../Components/Popover/PopoverExploration.swift | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Sources/Playbook/Components/Popover/PopoverCatalog.swift b/Sources/Playbook/Components/Popover/PopoverCatalog.swift index 6743480ac..27df4bbbf 100644 --- a/Sources/Playbook/Components/Popover/PopoverCatalog.swift +++ b/Sources/Playbook/Components/Popover/PopoverCatalog.swift @@ -171,7 +171,7 @@ public struct PopoverCatalog: View { } } - @State private var viewFrame: CGRect = CGRect(x: 200, y: 200, width: 0, height: 0) + @State private var viewFrame: CGRect = .zero private var explorationPopover: some View { HStack { Text("This is an exploration Popover") @@ -186,7 +186,7 @@ public struct PopoverCatalog: View { } .handlerPopoverController( isPresented: $isPresented7, - position: $viewFrame.origin + 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)) diff --git a/Sources/Playbook/Components/Popover/PopoverExploration.swift b/Sources/Playbook/Components/Popover/PopoverExploration.swift index cbaa023c4..98e7941d5 100644 --- a/Sources/Playbook/Components/Popover/PopoverExploration.swift +++ b/Sources/Playbook/Components/Popover/PopoverExploration.swift @@ -19,11 +19,12 @@ struct PopView: View { Text("I'm a popover. I can show content of any size.") .pbFont(.body, color: .text(.default)) } + } } extension View { - func handlerPopoverController(isPresented: Binding, position: Binding, @ViewBuilder content: @escaping (() -> Content)) -> some View { + func handlerPopoverController(isPresented: Binding, position: CGPoint, @ViewBuilder content: @escaping (() -> Content)) -> some View { self.background(PopHostingView(isPresented: isPresented, position: position, content: content)) } } @@ -33,7 +34,7 @@ import AppKit struct PopHostingView: NSViewRepresentable { @Binding var isPresented: Bool - @Binding var position: CGPoint + let position: CGPoint let content: () -> Content func makeNSView(context: Context) -> NSView { @@ -107,7 +108,7 @@ import UIKit struct PopHostingView: UIViewRepresentable { @Binding var isPresented: Bool - @Binding var position: CGPoint + let position: CGPoint let content: () -> Content func makeUIView(context: Context) -> UIView { @@ -124,26 +125,26 @@ struct PopHostingView: UIViewRepresentable { } func makeCoordinator() -> Coordinator { - Coordinator(isPresented: $isPresented, position: $position, content: content()) + Coordinator(isPresented: $isPresented, position: position, content: content()) } class Coordinator: NSObject, UIPopoverPresentationControllerDelegate { private let contentViewController: UIHostingController private weak var anchorView: UIView? @Binding var isPresented: Bool - @Binding var position: CGPoint + private let position: CGPoint var popoverView: UIView? - init(isPresented: Binding, position: Binding, content: Content) { + init(isPresented: Binding, position: CGPoint, content: Content) { self.contentViewController = UIHostingController(rootView: content) self._isPresented = isPresented - self._position = position + self.position = position super.init() let hostingView = UIHostingController(rootView: PopView()) let popoverView = hostingView.view - popoverView?.frame = CGRect(x: position.wrappedValue.x, y: position.wrappedValue.y, width: 500, height: 500) + popoverView?.frame = CGRect(x: position.x, y: position.y, width: 200, height: 100) popoverView?.layer.cornerRadius = 10 popoverView?.backgroundColor = .clear self.popoverView = popoverView From e0d00c38198e43c9df140c5a96c6491fcaf84fb6 Mon Sep 17 00:00:00 2001 From: "isis.silva" Date: Thu, 21 Nov 2024 17:57:23 -0500 Subject: [PATCH 4/4] add dismiss function --- .../Components/Popover/PopoverCatalog.swift | 12 +++++++++++- .../Components/Popover/PopoverExploration.swift | 13 ++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Sources/Playbook/Components/Popover/PopoverCatalog.swift b/Sources/Playbook/Components/Popover/PopoverCatalog.swift index 27df4bbbf..2eade5316 100644 --- a/Sources/Playbook/Components/Popover/PopoverCatalog.swift +++ b/Sources/Playbook/Components/Popover/PopoverCatalog.swift @@ -30,6 +30,9 @@ public struct PopoverCatalog: View { .padding(.bottom, 500) .edgesIgnoringSafeArea(.all) } + .onTapGesture { + isPresented7 = false + } .popoverHandler(id: 1) .popoverHandler(id: 2) .popoverHandler(id: 3) @@ -171,7 +174,7 @@ public struct PopoverCatalog: View { } } - @State private var viewFrame: CGRect = .zero + @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") @@ -192,5 +195,12 @@ public struct PopoverCatalog: View { .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 index 98e7941d5..69184116b 100644 --- a/Sources/Playbook/Components/Popover/PopoverExploration.swift +++ b/Sources/Playbook/Components/Popover/PopoverExploration.swift @@ -10,6 +10,7 @@ import SwiftUI struct PopView: View { + @Binding var isPresented: Bool var body: some View { PBCard( border: false, @@ -19,13 +20,19 @@ struct PopView: View { 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 } } @@ -91,7 +98,7 @@ struct PopHostingView: NSViewRepresentable { class PopViewController: NSViewController { override func loadView() { - let hostingView = NSHostingView(rootView: PopView()) + let hostingView = NSHostingView(rootView: PopView(isPresented: .constant(false))) hostingView.layoutSubtreeIfNeeded() let contentSize = hostingView.fittingSize let popoverView = NSView(frame: CGRect(origin: .zero, size: contentSize)) @@ -142,7 +149,7 @@ struct PopHostingView: UIViewRepresentable { self.position = position super.init() - let hostingView = UIHostingController(rootView: PopView()) + 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