Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[PBIOS-624] Popover investigation #473

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion Sources/Playbook/Components/Popover/PopoverCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}

Expand All @@ -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)
Expand Down Expand Up @@ -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()
}
176 changes: 176 additions & 0 deletions Sources/Playbook/Components/Popover/PopoverExploration.swift
Original file line number Diff line number Diff line change
@@ -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<Content: View>(isPresented: Binding<Bool>, 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<Content: View>: 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<Content>
@Binding var isPresented: Bool

init(isPresented: Binding<Bool>, 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<Content: View>: 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<Content>
private weak var anchorView: UIView?
@Binding var isPresented: Bool
private let position: CGPoint

var popoverView: UIView?

init(isPresented: Binding<Bool>, 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