Popovers
A library to present popovers. Simple, modern, and highly customizable. Not boring!
Install / Use
/learn @aheze/PopoversREADME

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. - Drop-in replacement for iOS 14's
Menuthat works on iOS 13. - SwiftUI-based core for a lightweight structure. 0 dependencies.
- It's 2023 — about time that popovers got interesting!
Showroom
<table> <tr> <td> Alert </td> <td> Color </td> <td> Menu </td> <td> Tip </td> <td> Standard </td> </tr> <tr> </tr> <tr> <td> <img src="Assets/GIFs/Alert.gif" alt="Alert"> </td> <td> <img src="Assets/GIFs/Color.gif" alt="Color"> </td> <td> <img src="Assets/GIFs/Menu.gif" alt="Menu"> </td> <td> <img src="Assets/GIFs/Tip.gif" alt="Tip"> </td> <td> <img src="Assets/GIFs/Standard.gif" alt="Standard"> </td> </tr> <tr> </tr> <tr> <td colspan=2> Tutorial </td> <td colspan=2> Picture-in-Picture </td> <td> Notification </td> </tr> <tr> </tr> <tr> <td colspan=2> <img src="Assets/GIFs/Tutorial.gif" alt="Tutorial"> </td> <td colspan=2> <img src="Assets/GIFs/PIP.gif" alt="Picture in Picture"> </td> <td> <img src="Assets/GIFs/Notification.gif" alt="Notification"> </td> </tr> </table>Example
Includes ~20 popover examples. Download

Installation
Requires iOS 13+. Popovers can be installed through the Swift Package Manager (recommended) or Cocoapods.
<table> <tr> <td> <strong> Swift Package Manager </strong> <br> Add the Package URL: </td> <td> <strong> Cocoapods </strong> <br> Add this to your Podfile: </td> </tr> <tr> <td> <br>https://github.com/aheze/Popovers
</td>
<td>
<br>
pod 'Popovers'
</td>
</tr>
</table>
Usage
To present a popover in SwiftUI, use the .popover(present:attributes:view) modifier. By default, the popover uses its parent view as the source frame.
import SwiftUI
import Popovers
struct ContentView: View {
@State var present = false
var body: some View {
Button("Present popover!") {
present = true
}
.popover(present: $present) { /// here!
Text("Hi, I'm a popover.")
.padding()
.foregroundColor(.white)
.background(.blue)
.cornerRadius(16)
}
}
}
In UIKit, create a Popover instance, then present with UIViewController.present(_:). You should also set the source frame.
import SwiftUI
import Popovers
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBAction func buttonPressed(_ sender: Any) {
var popover = Popover { PopoverView() }
popover.attributes.sourceFrame = { [weak button] in
button.windowFrame()
}
present(popover) /// here!
}
}
struct PopoverView: View {
var body: some View {
Text("Hi, I'm a popover.")
.padding()
.foregroundColor(.white)
.background(.blue)
.cornerRadius(16)
}
}
<img src="Assets/UsagePopover.png" width=300 alt="Button 'Present popover!' with a popover underneath.">
<br>
Customization
| 🔖 | 💠 | ⬜ | 🔲 | ⏹ | 🟩 | 🟥 | 🎾 | 🛑 | 👓 | 👉 | 🎈 | 🔰 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
Customize popovers through the Attributes struct. Pretty much everything is customizable, including positioning, animations, and dismissal behavior.
.popover(
present: $present,
attributes: {
$0.position = .absolute(
originAnchor: .bottom,
popoverAnchor: .topLeft
)
}
) {
Text("Hi, I'm a popover.")
}
</td>
<td>
<br>
var popover = Popover {
Text("Hi, I'm a popover.")
}
popover.attributes.position = .absolute(
originAnchor: .bottom,
popoverAnchor: .topLeft
)
present(popover)
</td>
</tr>
</table>
🔖 Tag • AnyHashable?
Tag popovers to access them later from anywhere. This is useful for updating existing popovers.
/// Set the tag.
$0.tag = "Your Tag"
/// Access it later.
let popover = popover(tagged: "Your Tag") /// Where `self` is a `UIView` or `UIViewController`.
/// If inside a SwiftUI View, use a `WindowReader`:
WindowReader { window in
let popover = window.popover(tagged: "Your Tag")
}
Note: When you use the .popover(selection:tag:attributes:view:) modifier, this tag is automatically set to what you provide in the parameter.
💠 Position • Position
The popover's position can either be .absolute (attached to a view) or .relative (picture-in-picture). The enum's associated value additionally configures which sides and corners are used.
Anchors represent sides and corners.- For
.absolute, provide the origin anchor and popover anchor. - For
.relative, provide the popover anchors. If there's multiple, the user will be able to drag between them like a PIP.
Anchor Reference | .absolute(originAnchor: .bottom, popoverAnchor: .topLeft) | .relative(popoverAnchors: [.right])
--- | --- | ---
|
| 
⬜ Source Frame • (() -> CGRect)
This is the frame that the popover attaches to or is placed within, depending on its position. This must be in global window coordinates. Because frames are can change so often, this property is a closure. Whenever the device rotates or some other bounds update happens, the closure will be called.
<table> <tr> <td> <strong> SwiftUI </strong> <br> By default, the source frame is automatically set to the parent view. Setting this will override it. </td> <td> <strong> UIKit </strong> <br> It's highly recommended to provide a source frame, otherwise the popover will appear in the top-left of the screen. </td> </tr> <tr> <td> <br>$0.sourceFrame = {
/** some CGRect here */
}
</td>
<td>
<br>
/// use `weak` to prevent a retain cycle
attributes.sourceFrame = { [weak button] in
button.windowFrame()
}
</td>
</tr>
</table>
🔲 Source Frame Inset • UIEdgeInsets
Edge insets to apply to the source frame. Positive values inset the frame, negative values expand it.
Absolute | Relative
--- | ---
| 
⏹ Screen Edge Padding • UIEdgeInsets
Global insets for all popovers to prevent them from overflowing off the screen. Kind of like a safe area. Default value is UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16).
🟩 Presentation • Presentation
This property stores the animation and transition that's applied when the popover appears.
/// Default values:
$0.presentation.animation = .easeInOut
$0.presentation.transition = .opacity
🟥 Dismissal • Dismissal
This property stores the popover's dismissal behavior. There's a couple sub-properties here.
/// Same thing as `Presentation`.
$0.dismissal.animation = .easeInOut
$0.dismissal.transition = .opacity
/// Advanced stuff! Here's their default values:
$0.dismissal.mode = .tapOutside
$0.dismissal.tapOutsideIncludesOtherPopovers = false
$0.dismissal.excludedFrames = { [] }
$0.dismissal.dragMovesPopoverOffScreen = true
$0.dismissal.dragDismissalProximity = CGFloat(0.25)
Mode: Configure how the popover should auto-dismiss. You can have multiple at the same time!
.tapOutside- dismiss the popover when the user taps outside it..dragDown- dismiss the popover when the user drags it down..dragUp- dismiss the popover when the user drags it up..none- don't automatically dismiss the popover.
Tap Outside Includes Other Popovers: Only applies when mode is .tapOutside. If this is enabled, the popover will be dismissed when the user taps outside, even when another presented popover is what's tapped. Normally when you tap another popover that's presented, the current one will not dismiss.
Excluded Frames: Only applies when mode is .tapOutside. When the user taps outside the popover, but the tap lands on one of these frames, the popover will stay presented. If you want multiple popovers, you should set the source frames of your other popovers as the excluded frames.
/// Set one popover's source frame as the other's excluded frame.
/// This prevents the the current popover from bein
