Skip to content

exyte/PopupView

Repository files navigation

     

Floaters Toasts Popups Sheets

Popup View

Toasts, alerts and popups library written with SwiftUI

Read Article »

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.

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
  • 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: determines the direction of appearing animation. If left empty it copies position parameter: so appears from .top edge, if position is set to .top disappearTo - same as appearFrom, but for disappearing animation. If left empty it copies appearFrom. animation - custom animation for popup sliding onto screen
autohideIn - time after which popup should disappear
dragToDismiss - true by default: enable/disable drag to dismiss (upwards for .top popup types, downwards for .bottom and default type)
closeOnTap - true by default: enable/disable closing on tap on popup
closeOnTapOutside - false by default: enable/disable closing on tap on outside of popup
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)
isOpaque - false by default: if true taps do not pass through popup's background and the popup is displayed on top of navbar. For more see section "Show over navbar"
useKeyboardSafeArea - false by default: if true popup goes up for keyboardHeight when keyboard is displayed dismissCallback - custom callback to call once the popup is dismissed

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