SkillAgentSearch skills...

PopupView

Toasts and popups library written with SwiftUI

Install / Use

/learn @exyte/PopupView
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<a href="https://exyte.com/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/header-dark.png"><img src="https://raw.githubusercontent.com/exyte/media/master/common/header-light.png"></picture></a>

<a href="https://exyte.com/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/our-site-dark.png" width="80" height="16"><img src="https://raw.githubusercontent.com/exyte/media/master/common/our-site-light.png" width="80" height="16"></picture></a>     <a href="https://twitter.com/exyteHQ"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/twitter-dark.png" width="74" height="16"><img src="https://raw.githubusercontent.com/exyte/media/master/common/twitter-light.png" width="74" height="16"> </picture></a> <a href="https://exyte.com/contacts"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/get-in-touch-dark.png" width="128" height="24" align="right"><img src="https://raw.githubusercontent.com/exyte/media/master/common/get-in-touch-light.png" width="128" height="24" align="right"></picture></a>

<table> <thead> <tr> <th>Floaters</th> <th>Toasts</th> <th>Popups</th> <th>Sheets</th> </tr> </thead> <tbody> <tr> <td> <img src="https://raw.githubusercontent.com/exyte/media/master/PopupView/1.gif" /> </td> <td> <img src="https://raw.githubusercontent.com/exyte/media/master/PopupView/2.gif" /> </td> <td> <img src="https://raw.githubusercontent.com/exyte/media/master/PopupView/3.gif" /> </td> <td> <img src="https://raw.githubusercontent.com/exyte/media/master/PopupView/4.gif" /> </td> </tr> </tbody> </table> <p><h1 align="left">Popup View</h1></p> <p><h4>Toasts, alerts and popups library written with SwiftUI</h4></p>

<a href="https://exyte.com/blog/swiftui-tutorial-popupview-library">Read Article »</a>

SPM Cocoapods License: MIT

What's new in version 4

You can show multiple popups on top of anything, and they can also let the taps pass through to lower views. There are 3 ways to display a popup: as a simple overlay, using SwiftUI's fullscreenSheet, and using UIKit's UIWindow. There are pros and cons for all of these, here is a table.

<table> <thead> <tr> <th></th> <th>Overlay</th> <th>Sheet</th> <th>Window</th> </tr> </thead> <tbody> <tr align=center> <th>Show on top of navbar</th> <td> ❌ </td> <td> ✅ </td> <td> ✅ </td> </tr> <tr align=center> <th>Show on top of sheet</th> <td> ❌ </td> <td> ❌ </td> <td> ✅ </td> </tr> <tr align=center> <th>Show multiple popups</th> <td> ✅ </td> <td> ❌ </td> <td> ✅ </td> </tr> <tr align=center> <th>Taps "pass through" the transparent bg</th> <td> ✅ </td> <td> ❌ </td> <td> ✅ </td> </tr> <tr align=center> <th>SwiftUI @State update mechanism works as expected</th> <td> ✅ </td> <td> ✅ </td> <td> ❌ </td> </tr> </tbody> </table>

Basically UIWindow based popup is the best option for most situations, just remember - to get adequate UI updates, use ObservableObjects or @Bindings instead of @State. This won't work:

struct ContentView : View {
    @State var showPopup = false
    @State var a = false

    var body: some View {
        Button("Button") {
            showPopup.toggle()
        }
        .popup(isPresented: $showPopup) {
            VStack {
                Button("Switch a") {
                    a.toggle()
                }
                a ? Text("on").foregroundStyle(.green) : Text("off").foregroundStyle(.red)
            }
        } customize: {
            $0
                .type(.floater())
                .closeOnTap(false)
                .position(.top)
        }
    }
}

This will work:

struct ContentView : View {
    @State var showPopup = false
    @State var a = false

    var body: some View {
        Button("Button") {
            showPopup.toggle()
        }
        .popup(isPresented: $showPopup) {
            PopupContent(a: $a)
        } customize: {
            $0
                .type(.floater())
                .closeOnTap(false)
                .position(.top)
        }
    }
}

struct PopupContent: View {
    @Binding var a: Bool

    var body: some View {
        VStack {
            Button("Switch a") {
                a.toggle()
            }
            a ? Text("on").foregroundStyle(.green) : Text("off").foregroundStyle(.red)
        }
    }
}

Update to version 4

New DisplayMode enum was introduced instead of isOpaque. isOpaque is now deprecated. Instead of:

.popup(isPresented: $toasts.showingTopSecond) {
    ToastTopSecond()
} customize: {
    $0
        .type(.toast)
        .isOpaque(true) // <-- here
}

use:

.popup(isPresented: $floats.showingTopFirst) {
    FloatTopFirst()
} customize: {
    $0
        .type(.floater())
        .displayMode(.sheet) // <-- here
}

So, new .displayMode(.sheet) corresponds to old .isOpaque(true), .displayMode(.overlay) corresponds to .isOpaque(false). Default DisplayMode is .window.

What's new in version 3

  • zoom in/out appear/disappear animations
  • disappearTo parameter to specify disappearing animation direction - can be different from appearFrom

Update to version 3

To include new .zoom type, AppearFrom enum cases were renamed. Instead of:

.popup(isPresented: $floats.showingTopFirst) {
    FloatTopFirst()
} customize: {
    $0
        .type(.floater())
        .appearFrom(.top) // <-- here
}

use:

.popup(isPresented: $floats.showingTopFirst) {
    FloatTopFirst()
} customize: {
    $0
        .type(.floater())
        .appearFrom(.topSlide) // <-- here
}

Update to version 2

Instead of:

.popup(isPresented: $floats.showingTopFirst, type: .floater(), position: .top, animation: .spring(), closeOnTapOutside: true, backgroundColor: .black.opacity(0.5)) {
    FloatTopFirst()
}

use:

.popup(isPresented: $floats.showingTopFirst) {
    FloatTopFirst()
} customize: {
    $0
        .type(.floater())
        .position(.top)
        .animation(.spring())
        .closeOnTapOutside(true)
        .backgroundColor(.black.opacity(0.5))
}

Using this API you can pass parameters in any order you like.

Usage

  1. Add a bool to control popup presentation state
  2. Add .popup modifier to your view.
import PopupView

struct ContentView: View {

    @State var showingPopup = false

    var body: some View {
        YourView()
            .popup(isPresented: $showingPopup) {
                Text("The popup")
                    .frame(width: 200, height: 60)
                    .background(Color(red: 0.85, green: 0.8, blue: 0.95))
                    .cornerRadius(30.0)
            } customize: {
                $0.autohideIn(2)
            }
    }
}

Required parameters

isPresented - binding to determine if the popup should be seen on screen or hidden
view - view you want to display on your popup

or

item - binding to item: if item's value is nil - popup is hidden, if non-nil - displayed. Be careful - library makes a copy of your item during dismiss animation!!
view - view you want to display on your popup

Available customizations - optional parameters

use customize closure in popup modifier:

type:

  • default - usual popup in the center of screen
  • toast - fitted to screen i.e. without padding and ignoring safe area
  • floater - has padding and can choose to use or ignore safe area
  • scroll - adds a scroll to your content, if you scroll to top of this scroll - the gesture will continue into popup's drag dismiss.

floater parameters:

  • verticalPadding - padding which will define padding from the relative vertical edge or will be added to safe area if useSafeAreaInset is true
  • horizontalPadding - padding which will define padding from the relative horizontal edge or will be added to safe area if useSafeAreaInset is true
  • useSafeAreaInset - whether to include safe area insets in floater padding

scroll parameters:
headerView - a view on top which won't be a part of the scroll (if you need one)

position - topLeading, top, topTrailing, leading, center, trailing, bottomLeading, bottom, bottomTrailing appearFrom - topSlide, bottomSlide, leftSlide, rightSlide, centerScale, none: determines the direction of appearing animation. If left empty it copies position parameter: so appears from .top edge, if `p

View on GitHub
GitHub Stars4.0k
CategoryDevelopment
Updated7h ago
Forks311

Languages

Swift

Security Score

100/100

Audited on Mar 31, 2026

No findings