Floaters | Toasts | Popups | Sheets |
---|---|---|---|
![]() ![]() |
![]() ![]() |
![]() ![]() |
![]() ![]() |
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)
}
}
}
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
.
- zoom in/out appear/disappear animations
disappearTo
parameter to specify disappearing animation direction - can be different fromappearFrom
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
}
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.
- Add a bool to control popup presentation state
- 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)
}
}
}
isPresented
- binding to determine if the popup should be seen on screen or hidden
view
- view you want to display on your popup
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
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 ifuseSafeAreaInset
is truehorizontalPadding
- padding which will define padding from the relative horizontal edge or will be added to safe area ifuseSafeAreaInset
is trueuseSafeAreaInset
- 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
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)
}
To try the PopupView examples:
- Clone the repo
https://github.com/exyte/PopupView.git
- Open
PopupExample.xcodeproj
in the Xcode - Try it!
dependencies: [
.package(url: "https://github.com/exyte/PopupView.git")
]
- iOS 15.0+ / macOS 11.0+ / tvOS 14.0+ / watchOS 7.0+
- Xcode 12+
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