SkillAgentSearch skills...

Popovers

A library to present popovers. Simple, modern, and highly customizable. Not boring!

Install / Use

/learn @aheze/Popovers
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Header Image

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 Menu that 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

Example app

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.

<table> <tr> <td> <strong> SwiftUI </strong> <br> Configure in the <code>attributes</code> parameter. </td> <td> <strong> UIKit </strong> <br> Modify the <code>attributes</code> property. </td> </tr> <tr> <td> <br>
.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 --- | --- Source view has padding around it, so the popover is offset down. | Source view is inset, so the popover is brought more towards the center of the screen.

⏹ 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
View on GitHub
GitHub Stars2.2k
CategoryDevelopment
Updated1d ago
Forks135

Languages

Swift

Security Score

100/100

Audited on Mar 23, 2026

No findings