Popover
A floating popover component that displays contextual content anchored to a trigger element. Features automatic edge detection, smooth spring animations, and multiple placement options.
Import
import PrettyUI
Basic Usage
The simplest way to create a popover is with a binding and content:
@State private var showPopover = false
var body: some View {
PButton("Show Popover") {
showPopover.toggle()
}
.pPopover(isPresented: $showPopover) {
Text("Hello from popover!")
.padding()
}
}
Placement
Control where the popover appears relative to its trigger:
@State private var showPopover = false
var body: some View {
VStack(spacing: 20) {
PButton("Top Placement") {
showPopover.toggle()
}
.pPopover(isPresented: $showPopover, placement: .top) {
Text("I appear above!")
.padding()
}
PButton("Bottom Placement") {
showPopover.toggle()
}
.pPopover(isPresented: $showPopover, placement: .bottom) {
Text("I appear below!")
.padding()
}
PButton("Leading Placement") {
showPopover.toggle()
}
.pPopover(isPresented: $showPopover, placement: .leading) {
Text("I appear to the left!")
.padding()
}
PButton("Trailing Placement") {
showPopover.toggle()
}
.pPopover(isPresented: $showPopover, placement: .trailing) {
Text("I appear to the right!")
.padding()
}
}
}
Available Placements
| Placement | Description |
|---|---|
.top | Above the trigger, centered horizontally |
.bottom | Below the trigger, centered horizontally |
.leading | To the left of the trigger |
.trailing | To the right of the trigger |
Arrow Indicator
The popover includes an arrow pointing to the trigger by default:
// With arrow (default)
.pPopover(isPresented: $showPopover, showArrow: true) {
Text("With arrow")
.padding()
}
// Without arrow
.pPopover(isPresented: $showPopover, showArrow: false) {
Text("No arrow")
.padding()
}
Dismiss on Outside Tap
Control whether tapping outside the popover dismisses it:
// Dismiss on outside tap (default)
.pPopover(isPresented: $showPopover, dismissOnOutsideTap: true) {
Text("Tap outside to dismiss")
.padding()
}
// Require explicit dismissal
.pPopover(isPresented: $showPopover, dismissOnOutsideTap: false) {
VStack {
Text("Must tap button to close")
PButton("Close") {
showPopover = false
}
}
.padding()
}
Custom Content
Popovers can contain any SwiftUI content:
@State private var showOptions = false
var body: some View {
PIconButton("ellipsis") {
showOptions.toggle()
}
.pPopover(isPresented: $showOptions, placement: .bottom) {
VStack(alignment: .leading, spacing: 0) {
PopoverMenuItem(icon: "pencil", title: "Edit")
PopoverMenuItem(icon: "doc.on.doc", title: "Duplicate")
PopoverMenuItem(icon: "folder", title: "Move to...")
Divider()
PopoverMenuItem(icon: "trash", title: "Delete", destructive: true)
}
.frame(width: 200)
}
}
struct PopoverMenuItem: View {
let icon: String
let title: String
var destructive: Bool = false
var body: some View {
Button {
// Action
} label: {
HStack(spacing: 12) {
Image(systemName: icon)
.frame(width: 20)
Text(title)
Spacer()
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
.foregroundColor(destructive ? .red : .primary)
}
.buttonStyle(.plain)
}
}
Menu-Style Popover
Create a menu with actions using the built-in styling:
@State private var showMenu = false
var body: some View {
PButton("Actions") {
showMenu.toggle()
}
.pPopover(isPresented: $showMenu, placement: .bottom) {
VStack(spacing: 0) {
ForEach(["Copy", "Share", "Export"], id: \.self) { action in
Button(action) {
showMenu = false
}
.padding(.horizontal, 20)
.padding(.vertical, 12)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.frame(width: 150)
}
}
Auto-Positioning
The popover automatically adjusts its position when near screen edges to stay visible. This behavior is built-in and requires no additional configuration.
Styling
The popover uses theme tokens for consistent styling:
- Background: Uses
cardcolor token - Border: Uses
bordercolor token - Shadow: Uses medium shadow from theme
- Corner Radius: Uses
lgradius token
Animation
The popover features smooth spring animations:
- Entry: Scales from 0.9 with fade-in
- Exit: Scales to 0.9 with fade-out
- Duration: Uses responsive spring physics
API Reference
View Modifier
func pPopover<Content: View>(
isPresented: Binding<Bool>,
placement: PPopoverPlacement = .bottom,
showArrow: Bool = true,
dismissOnOutsideTap: Bool = true,
@ViewBuilder content: @escaping () -> Content
) -> some View
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
isPresented | Binding<Bool> | Required | Controls visibility |
placement | PPopoverPlacement | .bottom | Preferred position |
showArrow | Bool | true | Show directional arrow |
dismissOnOutsideTap | Bool | true | Dismiss when tapping outside |
content | () -> Content | Required | Popover content view builder |
PPopoverPlacement
public enum PPopoverPlacement {
case top
case bottom
case leading
case trailing
}
Best Practices
- Keep content concise - Popovers are best for quick actions or brief information
- Use appropriate placement - Consider the context and available screen space
- Provide clear dismissal - Users should easily understand how to close the popover
- Limit nesting - Avoid opening popovers from within popovers