A lightweight SwiftUI toast notification system inspired by react-hot-toast, designed for simple and elegant user notifications.
- 🎯 Simple API - Clean, intuitive interface:
Toast.success("Hello!") - ⚡ High Performance - Smooth animations and optimized memory usage
- 📍 Flexible Positioning - Multiple positioning options (top, bottom, center)
- 🎨 Customizable - Theming, styling, and configuration support
- 🔄 Promise Support - Built-in async operation handling
- 📱 SwiftUI Native - Perfect integration with SwiftUI environment
// Package.swift
dependencies: [
.package(url: "https://github.com/jaywcjlove/Toast.git", from: "1.0.0")
]Enable Toast in your app:
import SwiftUI
import Toast
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.toast() // Enable toast container
}
}
}import Toast
struct ContentView: View {
var body: some View {
VStack {
Button("Success") {
// Static API (recommended)
Toast.success("Operation completed!")
}
Button("Error") {
Toast.error("Something went wrong")
}
Button("Info") {
Toast.info("Just so you know")
}
Button("Loading") {
Toast.loading("Processing...")
}
Button("Clear") {
Toast.dismissAll()
}
}
.toast() // Enable toast container
}
}// Method 1: Static API (works everywhere)
Toast.success("Success message")
Toast.error("Error message")
Toast.info("Info message")
Toast.loading("Loading message")
// Method 2: Full API (advanced usage)
ToastManager.shared.success("Success!")
ToastManager.shared.error("Error occurred")
ToastManager.shared.info("Information")
ToastManager.shared.loading("Loading...")// Available positions
Toast.success("Message", position: .topLeft)
Toast.info("Message", position: .topCenter) // default
Toast.error("Message", position: .topRight)
Toast.success("Message", position: .bottomLeft)
Toast.info("Message", position: .bottomCenter)
Toast.error("Message", position: .bottomRight)
Toast.loading("Message", position: .center)// Custom SwiftUI content
Toast.custom {
HStack {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
Text("Custom message")
.fontWeight(.bold)
}
.padding()
.background(Color.purple)
.foregroundColor(.white)
.cornerRadius(8)
}Automatic async operation state handling:
Toast.promise(
operation: {
// Your async operation
try await Task.sleep(for: .seconds(2))
return "Upload completed"
},
messages: .init(
loading: "Uploading...",
success: "Upload successful!",
error: "Upload failed"
)
)// Global configuration
ToastManager.shared.configuration.duration = 5.0
ToastManager.shared.configuration.position = .topCenter
// Per-toast configuration
// ⚠️ Static API doesn't support duration parameter
// Use ToastManager for custom duration:
ToastManager.shared.success("Custom duration", duration: 8.0)
ToastManager.shared.info("Manual dismiss", duration: 0) // Never auto-dismiss// Dismiss toasts
Toast.dismiss() // Dismiss latest toast
Toast.dismissAll() // Dismiss all toasts
// For ID-specific operations, use ToastManager:
ToastManager.shared.info("Loading...", id: "specific-id")
DispatchQueue.main.asyncAfter(deadline: .now() + 6) {
Toast.dismiss(id: "specific-id") // Dismiss by ID
}import SwiftUI
import Toast
struct ContentView: View {
@State private var counter = 0
var body: some View {
VStack(spacing: 20) {
Text("Toast Demo")
.font(.largeTitle)
// Basic toast types
HStack {
Button("Success") {
counter += 1
Toast.success("Success #\(counter)")
}
Button("Error") {
Toast.error("Something went wrong")
}
Button("Info") {
Toast.info("Information message")
}
}
// Loading and promise
Button("Loading") {
Toast.loading("Processing...")
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
Toast.success("Done!")
}
}
// Async promise
Button("Async Task") {
Toast.promise(
operation: {
try await Task.sleep(for: .seconds(2))
return "Task completed"
},
messages: .init(
loading: "Working...",
success: "Finished!",
error: "Failed"
)
)
}
// Custom toast
Button("Custom") {
Toast.custom {
HStack {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
Text("Custom Toast")
}
.padding()
.background(Color.purple)
.foregroundColor(.white)
.cornerRadius(8)
}
}
// Clear all
Button("Clear All") {
Toast.dismissAll()
}
.foregroundColor(.red)
}
.padding()
.toast() // Enable toast container
}
}This error occurs due to naming conflict between the global toast instance and the .toast() modifier method.
❌ Problem:
struct MyView: View {
var body: some View {
Button("Test") {
toast.success("Hello") // Error: naming conflict
}
.toast() // This creates the conflict
}
}✅ Solution (Recommended):
Use Static API (always works)
Button("Test") {
Toast.success("Hello") // ✅ Always reliable
}Alternative Solutions:
// Method 1: ToastManager directly
Button("Test") {
ToastManager.shared.success("Hello") // ✅ Always works
}
// Method 2: Only in completely separate contexts
class SomeService {
func performAction() {
// ✅ Works in non-SwiftUI contexts
toast.success("Service action completed")
}
}struct ToastConfiguration {
var position: ToastPosition = .topCenter
var duration: TimeInterval = 3.0
var theme: ToastTheme = .system
var maxVisibleToasts: Int = 3
var animationDuration: TimeInterval = 0.3
var spacing: CGFloat = 8
}Licensed under the MIT License.
