Skip to content
exytePublic

About

Toasts and popups library written with SwiftUI

Topics

Resources

Stars

4.1k stars

Watchers

23 watching

Forks

Repository files navigation

     

Floaters Toasts Popups Sheets

Popup View

Toasts, alerts and popups library written with SwiftUI

Read Article »

SPM Cocoapods License: MIT

Update to version 5

  • Moved all the popup types out of the main class with generic, to allow easier storing in variables, like this Popup.DisplayMode without Popup<V>.DisplayMode
  • DismissSource -> Popup.DismissSource
  • Scroll popup is not a part of PopupType enum anymore, it is created through a separate modifier, like this:
.scrollPopup(isPresented: $show) {
    YourPopupBody()
} header: {
    scrollViewHeader()
} customize: {
    $0
        .closeOnTap(false)
        .dragToDismiss(dragToDismiss)
}

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.

Overlay Sheet Window
Show on top of navbar ❌ ✅ ✅
Show on top of sheet ❌ ❌ ✅
Show multiple popups ✅ ❌ ✅
Taps "pass through" the transparent bg ✅ ❌ ✅
SwiftUI @State update mechanism works as expected ✅ ✅ ❌

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
    • verticalPadding - padding from the relative vertical edge, or added to safe area if useSafeAreaInset is true
    • horizontalPadding - padding from the relative horizontal edge, or added to safe area if useSafeAreaInset is true
    • useSafeAreaInset - whether to include safe area insets in floater padding

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: appears from .top edge if position is set to .top. .none means no animation
disappearTo - same as appearFrom, but for disappearing animation. If left empty it copies appearFrom
displayMode - how the popup is rendered: .overlay (placed above content), .sheet (using fullScreenCover), .window (using UIWindow, default)
animation - custom animation for popup sliding onto screen
autohideIn - time after which popup should disappear
dismissibleIn(Double?, Binding<Bool>?) - only allow dismiss after this time passes (forbids closeOnTap, closeOnTapOutside, and drag). Pass a boolean binding if you'd like to track current status
dragToDismiss - true by default: enable/disable drag to dismiss (upwards for .top popup types, downwards for .bottom and default type)
dragToDismissDistance - minimum distance to drag to trigger dismiss
closeOnTap - true by default: enable/disable closing on tap on popup.
NOTE: any gesture or control element you add to popup's body will override tap to close. in this case please close the popup manually if you need it to
closeOnTapOutside - false by default: enable/disable closing on tap outside of popup
allowTapThroughBG - false by default: should allow taps to pass "through" the popup's background down to views "below" it. .sheet popup is always allowTapThroughBG = false
backgroundColor - Color.clear by default: change background color of outside area
backgroundView - custom background builder for outside area (if this one is set, backgroundColor is ignored)
useKeyboardSafeArea - false by default: if true popup goes up for keyboardHeight when keyboard is displayed
becomesKeyWindow - true by default: only relevant for displayMode == .window. Set to false for transient, non-interactive popups (toasts/snackbars) to avoid stealing key window / first responder status — and with it the keyboard — from the presenting screen
willDismissCallback - called when dismiss animation starts
dismissCallback - called when dismiss animation ends

Scroll popup customizations

use customize closure in .scrollPopup modifier. In addition to all base customizations listed above:

position:

  • .bottom(_ topPadding: CGFloat) - default: positioned at the bottom with the specified top padding
  • .center(_ verticalPadding: CGFloat) - centered with the specified vertical padding

headerView - a view pinned to the top that is not part of the scroll

Draggable card - sheet

To implement a sheet (like in 4th gif) enable dragToDismiss on bottom toast (see example project for implementation of the card itself)

.popup(isPresented: $show) {
    // your content 
} customize: {
    $0
        .type (.toast)
        .position(.bottom)
        .dragToDismiss(true)
}

Examples

To try the PopupView examples:

  • Clone the repo https://github.com/exyte/PopupView.git
  • Open PopupExample.xcodeproj in the Xcode
  • Try it!

Installation

dependencies: [
    .package(url: "https://github.com/exyte/PopupView.git")
]

Requirements

  • iOS 15.0+ / macOS 11.0+ / tvOS 14.0+ / watchOS 7.0+
  • Xcode 12+

Our other open source SwiftUI libraries

AnchoredPopup - Anchored Popup grows "out" of a trigger view (similar to Hero animation)
Grid - The most powerful Grid container
ScalingHeaderScrollView - A scroll view with a sticky header which shrinks as you scroll
AnimatedTabBar - A tabbar with a number of preset animations
MediaPicker - Customizable media picker
Chat - Chat UI framework with fully customizable message cells, input view, and a built-in media picker
CalendarView - Calendar view with fully customizable month/day cells
OpenAI Wrapper lib for OpenAI REST API
AnimatedGradient - Animated linear gradient
ConcentricOnboarding - Animated onboarding flow
FloatingButton - Floating button menu
ActivityIndicatorView - A number of animated loading indicators
ProgressIndicatorView - A number of animated progress indicators
FlagAndCountryCode - Phone codes and flags for every country
SVGView - SVG parser
LiquidSwipe - Liquid navigation animation

About

Toasts and popups library written with SwiftUI

Topics

Resources

Stars

4.1k stars

Watchers

23 watching

Forks

Releases

Packages

Used by

Contributors

Languages