Skip to content

diegotl/toast-swift-ui

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

224 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Toast

A SwiftUI-native toast package for iOS, tvOS, and visionOS.

This project is a full rewrite of BastiaanJansen/toast-swift, replacing its UIKit implementation and API with a SwiftUI-first architecture. It does not use a UIKit-to-SwiftUI bridge or retain the previous public API.

Toast is designed around SwiftUI state, views, transitions, gestures, styles, Observation, and structured concurrency. It has no UIKit implementation, compatibility bridge, singleton, key-window lookup, delegate, or global presentation state.

Toast gallery

Requirements

  • Swift 6.2
  • iOS 15+ for the explicit compatibility API
  • iOS 17+ for the modern Observation-based API
  • tvOS 17+
  • visionOS 1+

The primary API documented below uses Observation and remains unchanged for iOS 17 and newer. Applications that support iOS 15 or iOS 16 use the isolated compatibility API described in iOS 15–16 Compatibility.

Installation

Add Toast with Swift Package Manager:

dependencies: [
    .package(
        url: "https://github.com/diegotl/toast-swift-ui.git",
        from: "3.0.0"
    )
]

Then add Toast to your target dependencies.

Install a host

Own a ToastCenter as stable SwiftUI state and install one host in the scene or subtree where toasts should appear:

import SwiftUI
import Toast

@main
struct ExampleApp: App {
    @State private var toastCenter = ToastCenter()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .toastHost(toastCenter)
        }
    }
}

The host injects its center into the descendant environment:

struct ContentView: View {
    @Environment(ToastCenter.self) private var toastCenter

    var body: some View {
        Button("Save") {
            toastCenter.show("Changes saved")
        }
    }
}

One center serves one active host. Use a separate center for each independent scene or subtree. Removing the active host cancels its delayed and queued presentations and dismisses visible toasts.

Built-in content

toastCenter.show(
    "AirPods Pro",
    subtitle: "Connected",
    systemImage: "airpodspro"
)

toastCenter.show(
    "Profile updated",
    image: Image(systemName: "person.crop.circle")
)

var title = AttributedString("Upload complete")
title.foregroundColor = .green

toastCenter.show(
    title,
    subtitle: AttributedString("Ready to share"),
    image: Image(systemName: "checkmark.circle.fill")
)

Built-in content uses semantic fonts and colors, adapts to light and dark appearances, supports Dynamic Type, and combines its title and subtitle for accessibility announcements.

Custom SwiftUI content

The builder accepts any SwiftUI view. Custom content uses the same placement, queue, overlay, transition, dismissal, feedback, and lifecycle systems as built-in content:

toastCenter.show(
    configuration: ToastConfiguration(
        placement: .center,
        dismissal: ToastDismissal(
            after: nil,
            onTap: true,
            swipe: .automatic
        )
    ),
    appearance: ToastAppearance(
        cornerRadius: 20,
        background: .material(.regular)
    )
) {
    Label("Custom SwiftUI", systemImage: "swift")
        .foregroundStyle(.orange)
}

Configuration

Defaults are top placement, replacement policy, a four-second timeout, natural swipe dismissal, automatic transitions, and a 0.2-second ease-out animation.

let configuration = ToastConfiguration(
    placement: .bottom,
    dismissal: ToastDismissal(
        after: .seconds(6),
        onTap: true,
        onLongPress: true,
        swipe: .automatic
    ),
    insertionTransition: .scaleAndOffset(
        scaleX: 0.9,
        scaleY: 0.9,
        x: 0,
        y: 24
    ),
    removalTransition: .opacity,
    animation: .easeInOut(duration: 0.3),
    overlay: .color(.black.opacity(0.12)),
    policy: .replace,
    feedback: .success
)

Placement

Use .top, .center, or .bottom. Placement stays inside the installed host's bounds and respects its safe area.

Presentation policy

  • .replace dismisses visible toasts and presents the newest toast.
  • .stack keeps presentations visible together at their configured placements.
  • .enqueue presents one toast at a time in insertion order.

Dismissal

ToastDismissal composes timeout, tap, long-press, and directional swipe behavior. Set after to nil to disable the timeout, set swipe to nil to disable drag dismissal, or use .manual for programmatic-only dismissal.

While a drag is active, the timeout is paused. A cancelled drag snaps back and restarts the full configured timeout.

let id = toastCenter.show(
    "Working",
    configuration: ToastConfiguration(dismissal: .manual)
)

toastCenter.dismiss(id: id)
toastCenter.dismissAll(animated: false)

Calling dismiss(id:) also cancels an item that is delayed or waiting in a queue.

Delayed presentation

let id = toastCenter.show(
    "Starting now",
    after: .seconds(1)
)

Delays and timeouts use cancellable Swift concurrency tasks rather than timers or dispatch queues.

Transitions

Insertion and removal transitions are independent. Built-in values are .automatic, .opacity, .offset, .scale, and .scaleAndOffset. Use .custom(AnyTransition) for any caller-defined SwiftUI transition. Automatic movement follows placement; Reduce Motion substitutes an opacity transition.

Appearance

ToastAppearance controls minimum or exact size, padding, content alignment, line limits, corner radius, adaptive color or material background, and shadow:

let appearance = ToastAppearance(
    minimumSize: CGSize(width: 180, height: 60),
    fixedSize: CGSize(width: 240, height: 80),
    padding: EdgeInsets(
        top: 12,
        leading: 16,
        bottom: 12,
        trailing: 16
    ),
    alignment: .leading,
    titleLineLimit: 2,
    subtitleLineLimit: nil,
    cornerRadius: 18,
    background: .material(.regular),
    shadow: ToastShadow(
        color: .black.opacity(0.1),
        radius: 10,
        y: 5
    )
)

Prefer content-driven sizing for compact layouts, multitasking, and accessibility text. Exact size remains available for intentionally fixed designs.

Lifecycle callbacks

let callbacks = ToastCallbacks(
    willPresent: { id in
        print("Will present", id)
    },
    didPresent: { id in
        print("Did present", id)
    },
    willDismiss: { id, reason in
        print("Will dismiss", id, reason)
    },
    didDismiss: { id, reason in
        print("Did dismiss", id, reason)
    }
)

toastCenter.show(
    "Uploaded",
    callbacks: callbacks
)

Dismissal reasons distinguish timeout, tap, long press, swipe, programmatic control, replacement, and host removal. Callbacks run on the main actor around the committed presentation-state change.

Styles

Apply a SwiftUI-native style at the host or any ancestor:

struct MinimalToastStyle: ToastStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.content
            .padding(configuration.appearance.padding)
            .background(.thinMaterial, in: .rect(cornerRadius: 16))
    }
}

ContentView()
    .toastHost(toastCenter)
    .toastStyle(MinimalToastStyle())

Styles receive the toast identity, content, and per-presentation appearance. No internal host view or platform view type is exposed.

Platform behavior

  • iOS supports timeout, tap, long press, directional swipe, accessibility announcements/actions, and SwiftUI sensory feedback.
  • tvOS omits drag dismissal, exposes tappable toasts as focusable buttons, and supports remote activation, long press, announcements, and timeouts.
  • visionOS supports SwiftUI interactions and adds sensory feedback when the running OS provides the API.

Unavailable capabilities are omitted at compile time; there is no UIKit fallback. Reduce Motion and Dynamic Type flow through the SwiftUI environment.

Example and tests

Examples/ToastShowcase contains a compiled cross-platform SwiftUI view and preview covering built-in, attributed, custom, stacked, queued, overlaid, and programmatically controlled presentations. README call sites are mirrored by that target or by Tests/ToastTests/API/APISurfaceTests.swift.

Examples/ToastVerificationApp is a runnable SwiftUI application target for iOS, tvOS, and visionOS. It exercises the full content, dismissal, policy, overlay, feedback, accessibility, and programmatic-control matrix against this package through a local Swift Package dependency. Its cross-platform UI test target provides repeatable end-to-end touch, remote, gesture, policy, and accessibility-environment checks.

Open Examples/ToastVerificationApp/ToastVerificationApp.xcodeproj and run the ToastVerificationApp scheme on the desired device or simulator.

The verification app remains an iOS 17+ demonstration of the modern API. ToastCompatibilityShowcase is compiled as part of the package to validate the iOS 15–16 integration surface.

License

Toast is available under the MIT license. See LICENSE.

About

Customizable Swift Toast view built with SwiftUI. 🍞

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages