# Popovers **Repository Path**: meouoo/Popovers ## Basic Information - **Project Name**: Popovers - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: feature/directly-add-as-subview - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-05-16 - **Last Updated**: 2025-05-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README  # Popovers A library to present popovers. - Present **any** view above your app's main content. - Attach to source views or use picture-in-picture positioning. - Display multiple popovers at the same time with smooth transitions. - Supports SwiftUI, UIKit, and multitasking windows on iPadOS. - Highly customizable API that's super simple — just add `.popover`. - Written in SwiftUI with 0 dependencies. ## Showroom
| Alert | Color | Menu | Tip | Standard |
|
|
|
|
|
| Tutorial | Picture-in-Picture | Notification | ||
|
|
|
||
|
Swift Package Manager
Add the Package URL: |
Cocoapods
Add this to your Podfile: |
|
``` https://github.com/aheze/Popovers ``` |
``` pod 'Popovers' ``` |
|
SwiftUI
Configure in the attributes parameter.
|
UIKit
Modify the attributes property.
|
|
```swift .popover( present: $present, attributes: { $0.position = .absolute( originAnchor: .bottom, popoverAnchor: .topLeft ) } ) { Text("Hi, I'm a popover.") } ``` |
```swift var popover = Popover { Text("Hi, I'm a popover.") } popover.attributes.position = .absolute( originAnchor: .bottom, popoverAnchor: .topLeft ) present(popover) ``` |
|
SwiftUI
By default, the source frame is automatically set to the parent view. Setting this will override it. |
UIKit
It's highly recommended to provide a source frame, otherwise the popover will appear in the top-left of the screen. |
|
```swift $0.sourceFrame = { /** some CGRect here */ } ``` |
```swift /// use `weak` to prevent a retain cycle attributes.sourceFrame = { [weak button] in button.windowFrame() } ``` |
### 🎾 Rubber Banding Mode • `RubberBandingMode`
Configures which axes the popover can "rubber-band" on when dragged. The default is `[.xAxis, .yAxis]`.
- `.xAxis` - enable rubber banding on the x-axis.
- `.yAxis` - enable rubber banding on the y-axis.
- `.none` - disable rubber banding.
### 🛑 Blocks Background Touches • `Bool`
Set this to true to prevent underlying views from being pressed.
### 👉 On Tap Outside • `(() -> Void)?`
A closure that's called whenever the user taps outside the popover.
### 🎈 On Dismiss • `(() -> Void)?`
A closure that's called when the popover is dismissed.
### 🔰 On Context Change • `((Context) -> Void)?`
A closure that's called whenever the context changed. The context contains the popover's attributes, current frame, and other visible traits.
|
SwiftUI
Use the .popover(selection:tag:attributes:view:) modifier.
|
UIKit
Get the existing popover using UIResponder.popover(tagged:), then call UIResponder.replace(_:with:).
|
|
```swift struct ContentView: View { @State var selection: String? var body: some View { HStack { Button("Present First Popover") { selection = "1" } .popover(selection: $selection, tag: "1") { /// Will be presented when selection == "1". Text("Hi, I'm a popover.") .background(.blue) } Button("Present Second Popover") { selection = "2" } .popover(selection: $selection, tag: "2") { /// Will be presented when selection == "2". Text("Hi, I'm a popover.") .background(.green) } } } } ``` |
```swift @IBAction func button1Pressed(_ sender: Any) { var newPopover = Popover { Text("Hi, I'm a popover.").background(.blue) } newPopover.attributes.sourceFrame = { [weak button1] in button1.windowFrame() } newPopover.attributes.dismissal.excludedFrames = { [weak button2] in [button2.windowFrame()] } newPopover.attributes.tag = "Popover 1" if let oldPopover = popover(tagged: "Popover 2") { replace(oldPopover, with: newPopover) } else { present(newPopover) /// Present if the old popover doesn't exist. } } @IBAction func button2Pressed(_ sender: Any) { var newPopover = Popover { Text("Hi, I'm a popover.").background(.green) } newPopover.attributes.sourceFrame = { [weak button2] in button2.windowFrame() } newPopover.attributes.dismissal.excludedFrames = { [weak button1] in [button1.windowFrame()] } newPopover.attributes.tag = "Popover 2" if let oldPopover = popover(tagged: "Popover 1") { replace(oldPopover, with: newPopover) } else { present(newPopover) } } ``` |
|
| --- |
### 🌃 Background
You can put anything in a popover's background.
|
SwiftUI
Use the .popover(present:attributes:view:background:) modifier.
|
UIKit
Use the Popover(attributes:view:background:) initializer.
|
|
```swift .popover(present: $present) { PopoverView() } background: { /// here! Color.green.opacity(0.5) } ``` |
```swift var popover = Popover { PopoverView() } background: { /// here! Color.green.opacity(0.5) } ``` |
### 📖 Popover Reader
This reads the popover's context, which contains its frame, window, attributes, and various other properties. It's kind of like [`GeometryReader`](https://www.hackingwithswift.com/quick-start/swiftui/how-to-provide-relative-sizes-using-geometryreader), but cooler. You can put it in the popover's view or its background.
```swift
.popover(present: $present) {
PopoverView()
} background: {
PopoverReader { context in
Path {
$0.move(to: context.frame.point(at: .bottom))
$0.addLine(to: context.windowBounds.point(at: .bottom))
}
.stroke(Color.blue, lineWidth: 4)
}
}
```
|
|
| --- |
### 🏷 Frame Tags
Popovers includes a mechanism for tagging and reading SwiftUI view frames. You can use this to provide a popover's `sourceFrame` or `excludedFrames`. Also works great when combined with `PopoverReader`, for connecting lines with anchor views.
```swift
Text("This is a view")
.frameTag("Your Tag Name") /// Adds a tag inside the window.
/// ...
WindowReader { window in
Text("Click me!")
.popover(
present: $present,
attributes: {
$0.sourceFrame = window.frameTagged("Your Tag Name") /// Retrieves a tag from the window.
}
)
}
```
### 📄 Templates
Get started quickly with some templates. All of them are inside [`PopoverTemplates.swift`](Source/PopoverTemplates.swift) with example usage in the example app.
- `AlertButtonStyle` - a button style resembling a system alert.
- `VisualEffectView` - lets you use UIKit blurs in SwiftUI.
- `ContainerShadow` - a view modifier that applies a system-like shadow.
- `Container` - a wrapper view for the `BackgroundWithArrow` shape.
- `BackgroundWithArrow` - a shape with an arrow that looks like the system popover.
- `CurveConnector` - an animatable shape with endpoints that you can set.
- `Menu` - the system menu but built from scratch.
- `MenuButton` - buttons to put in the `Menu`.
|
Yes
The popover's view is in a separate struct, with $string passed down.
|
No
The button is directly inside the view parameter and receives string.
|
|
```swift struct ContentView: View { @State var present = false @State var string = "Hello, I'm a popover." var body: some View { Button("Present popover!") { present = true } .popover(present: $present) { PopoverView(string: $string) /// Pass down a Binding ($). } } } /// Create a separate view to ensure that the button updates. struct PopoverView: View { @Binding var string: String var body: some View { Button(string) { string = "The string changed." } .background(.mint) .cornerRadius(16) } } ``` |
```swift struct ContentView: View { @State var present = false @State var string = "Hello, I'm a popover." var body: some View { Button("Present popover!") { present = true } .popover(present: $present) { /// Directly passing down the variable (without $) is unsupported. /// The button might not update. Button(string) { string = "The string changed." } .background(.mint) .cornerRadius(16) } } } ``` |