Skip to content

Instantly share code, notes, and snippets.

@luthviar
Last active September 18, 2025 06:23
Show Gist options
  • Select an option

  • Save luthviar/b2fa9be4413e0b278b7bde72f06cffdd to your computer and use it in GitHub Desktop.

Select an option

Save luthviar/b2fa9be4413e0b278b7bde72f06cffdd to your computer and use it in GitHub Desktop.
SimpleToastSwiftUI+UIKitReliable.swift
//
// ContentView22.swift
// Coba14July2025
//
// Created by Luthfi Abdurrahim on 18/09/25.
//
import SwiftUI
import Combine
import UIKit
// MARK: - Public API
enum SimpleToast {
/// Shows a toast for ~3 seconds above everything, even during navigation/presentations.
static func show(_ message: String) {
SimpleToastManager.shared.enqueue(message: message)
}
}
// MARK: - Manager
private final class SimpleToastManager {
static let shared = SimpleToastManager()
private var queue: [String] = []
private var isShowing = false
private var cancellables = Set<AnyCancellable>()
private let lock = NSLock()
private init() {
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
.sink { [weak self] _ in self?.drainIfPossible() }
.store(in: &cancellables)
}
func enqueue(message: String) {
lock.lock()
queue.append(message)
let canStart = !isShowing
lock.unlock()
if canStart { drainIfPossible() }
}
private func drainIfPossible() {
DispatchQueue.main.async { [weak self] in
guard let self else { return }
guard UIApplication.shared.applicationState == .active else { return }
lock.lock()
guard !isShowing, let next = queue.first else { lock.unlock(); return }
isShowing = true
lock.unlock()
OverlayWindow.shared.present(message: next, onHide: { [weak self] in
guard let self else { return }
self.lock.lock()
if !self.queue.isEmpty { _ = self.queue.removeFirst() }
self.isShowing = false
let hasMore = !self.queue.isEmpty
self.lock.unlock()
if hasMore { self.drainIfPossible() }
})
}
}
}
// MARK: - Overlay Window
private final class OverlayWindow: UIWindow {
static let shared = OverlayWindow()
private var hosting: UIHostingController<SimpleToastContainer>?
private init() {
// Pick an active window scene if available
let scene = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first { $0.activationState == .foregroundActive }
?? UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first
if let scene {
super.init(windowScene: scene)
} else {
super.init(frame: UIScreen.main.bounds)
}
windowLevel = .alert + 1 // above everything, including status bar & sheets
isHidden = true
backgroundColor = .clear
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func present(message: String, onHide: @escaping () -> Void) {
let root = SimpleToastContainer(message: message) { [weak self] in
self?.hideIfNeeded(completion: onHide)
}
let hc = UIHostingController(rootView: root)
hc.view.backgroundColor = .clear
rootViewController = hc
hosting = hc
isHidden = false
hc.view.alpha = 0
hc.view.transform = CGAffineTransform(translationX: 0, y: -12)
// Animate in
UIView.animate(withDuration: 0.28, delay: 0, options: [.curveEaseOut]) {
hc.view.alpha = 1
hc.view.transform = .identity
}
// Auto hide after ~3 seconds
let hideAfter = DispatchTime.now() + .seconds(3)
DispatchQueue.main.asyncAfter(deadline: hideAfter) { [weak self] in
self?.hideIfNeeded(completion: onHide)
}
}
private var isHiding = false
private func hideIfNeeded(completion: @escaping () -> Void) {
guard !isHiding, !isHidden else { return }
isHiding = true
guard let v = hosting?.view else {
cleanup()
completion()
return
}
UIView.animate(withDuration: 0.22, delay: 0, options: [.curveEaseIn]) {
v.alpha = 0
v.transform = CGAffineTransform(translationX: 0, y: -10)
} completion: { [weak self] _ in
self?.cleanup()
completion()
}
}
private func cleanup() {
isHiding = false
isHidden = true
rootViewController = nil
hosting = nil
}
// Allow taps to pass through to underlying app except when actually
// touching the visible toast content. This keeps the screen interactive
// while the toast is showing.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// If window is hidden, ignore completely
guard !isHidden else { return nil }
// Ask normal system first
let hit = super.hitTest(point, with: event)
// If the hit view is exactly the root hosting controller's root view
// (which spans the full screen) we treat it as background and
// let the event pass through (return nil). Any deeper SwiftUI
// subview (the toast bubble) should still receive touches.
if hit === rootViewController?.view { return nil }
return hit
}
}
// MARK: - SwiftUI View
private struct SimpleToastContainer: View {
let message: String
let onTapToDismiss: () -> Void
var body: some View {
ZStack {
// The toast pinned to safe top center
VStack {
Spacer().frame(height: 12)
HStack {
Text(message)
.font(.system(size: 15, weight: .semibold))
.foregroundColor(.white)
.padding(.horizontal, 25)
.padding(.vertical, 16)
}
.background(
RoundedRectangle(cornerRadius: .infinity, style: .continuous)
.fill(Color.black.opacity(0.7))
)
.overlay(
RoundedRectangle(cornerRadius: .infinity)
.stroke(Color.white.opacity(0.12), lineWidth: 0.5)
)
.shadow(radius: 4, y: 2)
//.contentShape(Rectangle())
.contentShape(RoundedRectangle(cornerRadius: .infinity))
.onTapGesture { onTapToDismiss() }
Spacer() // keep at top area
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.padding(.horizontal, 20)
.padding(.top, topSafeInset + 8)
.ignoresSafeArea()
}
.accessibilityElement(children: .ignore)
.accessibilityLabel(Text(message))
.accessibilityAddTraits(.isStaticText)
}
private var topSafeInset: CGFloat {
UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0
}
}
// MARK: - Example (remove in production)
// Example SwiftUI usage:
struct ContentView22: View {
@State private var shouldNavigate = false
var body: some View {
NavigationView {
VStack(spacing: 24) {
Button("Show Toast") {
SimpleToast.show("Link telah disalin!")
}
Button("should navigate") {
shouldNavigate = true
}
}
.padding()
.background {
NavigationLink(isActive: $shouldNavigate) {
DetailView()
} label: {
EmptyView()
}
}
}
}
}
struct DetailView: View {
var body: some View {
NavigationView {
VStack(spacing: 24) {
Text("Detail Screen")
Button("Show Toast here too") {
SimpleToast.show("Success! 🎉")
}
}
}
}
}
@luthviar
Copy link
Author

IMG_1552

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment