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.
- 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.
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.
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.
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.
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)
}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
)Use .top, .center, or .bottom. Placement stays inside the installed
host's bounds and respects its safe area.
.replacedismisses visible toasts and presents the newest toast..stackkeeps presentations visible together at their configured placements..enqueuepresents one toast at a time in insertion order.
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.
let id = toastCenter.show(
"Starting now",
after: .seconds(1)
)Delays and timeouts use cancellable Swift concurrency tasks rather than timers or dispatch queues.
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.
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.
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.
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.
- 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.
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.
Toast is available under the MIT license. See LICENSE.
