Skip to content

A lightweight SwiftUI toast notification system inspired

License

Notifications You must be signed in to change notification settings

jaywcjlove/Toast

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Using my apps is also a way to support me:
Screen Test Deskmark Keyzer Vidwall Hub VidCrop Vidwall Mousio Hint Mousio Musicer Audioer FileSentinel FocusCursor Videoer KeyClicker DayBar Iconed Mousio Quick RSS Quick RSS Web Serve Copybook Generator DevTutor for SwiftUI RegexMate Time Passage Iconize Folder Textsound Saver Create Custom Symbols DevHub Resume Revise Palette Genius Symbol Scribe

Toast

A lightweight SwiftUI toast notification system inspired by react-hot-toast, designed for simple and elegant user notifications.

Toast

✨ Features

  • 🎯 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

🚀 Quick Start

1. Installation

// Package.swift
dependencies: [
    .package(url: "https://github.com/jaywcjlove/Toast.git", from: "1.0.0")
]

2. Setup

Enable Toast in your app:

import SwiftUI
import Toast

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .toast() // Enable toast container
        }
    }
}

3. Basic Usage

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
    }
}

API Reference

Basic Messages

// 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...")

Positioning

// 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 Toasts

// 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)
}

Promise Support

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"
    )
)

Configuration

// 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

Control Methods

// 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
}

Complete Example

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
    }
}

Troubleshooting

"Use of 'toast' refers to instance method" Error

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")
    }
}

Configuration Options

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
}

License

Licensed under the MIT License.