PopupView
Toasts and popups library written with SwiftUI
Install / Use
/learn @exyte/PopupViewREADME
<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>
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
disappearToparameter to specify disappearing animation direction - can be different fromappearFrom
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
- Add a bool to control popup presentation state
- Add
.popupmodifier 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 ifuseSafeAreaInsetis truehorizontalPadding- padding which will define padding from the relative horizontal edge or will be added to safe area ifuseSafeAreaInsetis 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, none: determines the direction of appearing animation. If left empty it copies position parameter: so appears from .top edge, if `p
