- August 22, 2025
- Mins Read
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)
}
}
}
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
.
disappearTo
parameter to specify disappearing animation direction – can be different from appearFrom
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.
.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 screenfloater parameters:
verticalPadding
– padding which will define padding from the relative vertical edge or will be added to safe area if useSafeAreaInset
is truehorizontalPadding
– padding which will define padding from the relative horizontal edge or will be added to safe area if useSafeAreaInset
is trueuseSafeAreaInset
– whether to include safe area insets in floater paddingscroll 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 position
is set to .top. .none
means no animation 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
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)
closeOnTap
– true by default: enable/disable closing on tap on popup
closeOnTapOutside
– false by default: enable/disable closing on tap on outside of popup
allowTapThroughBG
– 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)
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:
https://github.com/exyte/PopupView.git
PopupExample.xcodeproj
in the Xcode
dependencies: [
.package(url: “https://github.com/exyte/PopupView.git”)
]
A SwiftUI Marquee or "scrolling text" effect found in Apple native apps. For when one line isn't enough, but two ...
Introduction Text composition in SwiftUI can often be cumbersome, especially when there's logic affecting its format and content. TextBuilder leverages the ...