How to build a simple toast message view in SwiftUI
In this tutorial, we will walk through the steps of creating a simple toast message view in SwiftUI. You’ll have a working toast message view that can be easily integrated into your SwiftUI projects.
First, we will create a Toast
struct to store configurable details that we want to make available.
struct Toast: Equatable {
var style: ToastStyle
var message: String
var duration: Double = 3
var width: Double = .infinity
}
Next, we will create two computed properties to determine the color and system icon name based on the toast’s style.
import SwiftUI
enum ToastStyle {
case error
case warning
case success
case info
}
extension ToastStyle {
var themeColor: Color {
switch self {
case .error: return Color.red
case .warning: return Color.orange
case .info: return Color.blue
case .success: return Color.green
}
}
var iconFileName: String {
switch self {
case .info: return "info.circle.fill"
case .warning: return "exclamationmark.triangle.fill"
case .success: return "checkmark.circle.fill"
case .error: return "xmark.circle.fill"
}
}
}
Now, we will construct the toast view.
import SwiftUI
struct ToastView: View {
var style: ToastStyle
var message: String
var width = CGFloat.infinity
var onCancelTapped: (() -> Void)
var body: some View {
HStack(alignment: .center, spacing: 12) {
Image(systemName: style.iconFileName)
.foregroundColor(style.themeColor)
Text(message)
.font(Font.caption)
.foregroundColor(Color("toastForeground"))
Spacer(minLength: 10)
Button {
onCancelTapped()
} label: {
Image(systemName: "xmark")
.foregroundColor(style.themeColor)
}
}
.padding()
.frame(minWidth: 0, maxWidth: width)
.background(Color("toastBackground"))
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.opacity(0.6)
)
.padding(.horizontal, 16)
}
}
One of the final steps is to create a view modifier for displaying and dismissing the toast.
import SwiftUI
struct ToastModifier: ViewModifier {
@Binding var toast: Toast?
@State private var workItem: DispatchWorkItem?
func body(content: Content) -> some View {
content
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay(
ZStack {
mainToastView()
.offset(y: 32)
}.animation(.spring(), value: toast)
)
.onChange(of: toast) { value in
showToast()
}
}
@ViewBuilder func mainToastView() -> some View {
if let toast = toast {
VStack {
ToastView(
style: toast.style,
message: toast.message,
width: toast.width
) {
dismissToast()
}
Spacer()
}
}
}
private func showToast() {
guard let toast = toast else { return }
UIImpactFeedbackGenerator(style: .light)
.impactOccurred()
if toast.duration > 0 {
workItem?.cancel()
let task = DispatchWorkItem {
dismissToast()
}
workItem = task
DispatchQueue.main.asyncAfter(deadline: .now() + toast.duration, execute: task)
}
}
private func dismissToast() {
withAnimation {
toast = nil
}
workItem?.cancel()
workItem = nil
}
}
We’ll also add a helper function that links the view modifier and our Toast
struct instance.
import SwiftUI
extension View {
func toastView(toast: Binding<Toast?>) -> some View {
self.modifier(ToastModifier(toast: toast))
}
}
Now we can use it to present toast messages.
import SwiftUI
struct ContentView: View {
@State private var toast: Toast? = nil
var body: some View {
VStack(spacing: 32) {
Button {
toast = Toast(style: .success, message: "Saved.", width: 160)
} label: {
Text("Run (Success)")
}
Button {
toast = Toast(style: .info, message: "Btw, you are a good person.")
} label: {
Text("Run (Info)")
}
Button {
toast = Toast(style: .warning, message: "Beware of a dog!")
} label: {
Text("Run (Warning)")
}
Button {
toast = Toast(style: .error, message: "Fatal error, blue screen level.")
} label: {
Text("Run (Error)")
}
}
.toastView(toast: $toast)
}
}
Demo
Code is available on Github: https://github.com/ondrej-kvasnovsky/SimpleToastDemo
References & materials
- https://betterprogramming.pub/swiftui-create-a-fancy-toast-component-in-10-minutes-e6bae6021984
- https://github.com/ondrej-kvasnovsky/toast-demo
- https://stackoverflow.com/questions/62124305/how-to-use-toast-swift-in-swiftui
- https://stackoverflow.com/questions/56550135/swiftui-global-overlay-that-can-be-triggered-from-any-view/69342575#69342575
- https://github.com/elai950/AlertToast
- https://bootcamp.uxdesign.cc/when-should-we-toast-use-the-most-fix-ux-353def0e61a5
- https://dribbble.com/tags/toast%20message
- https://uxplanet.org/notification-banners-for-building-trust-factor-in-enterprise-ux-c73e35de83e2
- https://medium.com/ringcentral-ux/notifications-messages-and-error-handling-part-1-5889f98bb947
- https://ux.stackexchange.com/questions/64788/floating-versus-inline-alerts
- https://uxdesign.cc/are-you-sure-you-want-to-do-this-microcopy-for-confirmation-dialogues-1d94a0f73ac6