From dbd3be081702c2532e51673ce230aed2369b92f8 Mon Sep 17 00:00:00 2001 From: gambit1185_church Date: Mon, 1 Jun 2026 13:28:03 -0600 Subject: [PATCH] feat: add a custom display mode for arbitrary toast content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `DisplayMode.custom` and a `customView` initializer so callers can present any SwiftUI view as a toast: AlertToast { HStack { ProgressView(); Text("Uploading…") } } The custom view is presented centered and animated exactly like `.alert` (the two cases share the same presentation path), and it respects the existing `duration` / `tapToDismiss` behavior. A `style:` argument is still accepted for background / glass customization. Reimplements the idea from fanxiangyang's elai950/AlertToast#113 on top of the current code (the original branch predated the visionOS, subtitle, and glass changes and bundled an unrelated timer-cancel change). Adds a unit test and a README "Custom view" section. Co-Authored-By: fanxiangyang Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 16 ++++++++ Sources/AlertToast/AlertToast.swift | 41 ++++++++++++++++++--- Tests/AlertToastTests/AlertToastTests.swift | 10 +++++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ad62023..4b152e2 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ AlertToast(displayMode: DisplayMode = .alert, - **Alert:** pop at the center of the screen. - **HUD:** drop from the top of the screen. - **Banner:** pop/slide from the bottom of the screen. +- **Custom:** present any view of your own at the center of the screen (see [Custom view](#custom-view)). #### Available alert types: - **Regular:** text only (title and subtitle). @@ -238,6 +239,21 @@ AlertToast(type: .image(String, Color), title: Optional(String), subTitle: Optio AlertToast(type: .loading, title: Optional(String), subTitle: Optional(String)) ``` +#### Custom view: +Present any view you like using the `customView` initializer. It is shown centered like `.alert` and respects `duration` / `tapToDismiss`. + +```swift +.toast(isPresenting: $showToast) { + AlertToast { + HStack { + ProgressView() + Text("Uploading…") + .font(.headline) + } + } +} +``` + You can add many `.toast` modifiers on a single view. ## 📖 Article diff --git a/Sources/AlertToast/AlertToast.swift b/Sources/AlertToast/AlertToast.swift index 4085781..f72c6f1 100644 --- a/Sources/AlertToast/AlertToast.swift +++ b/Sources/AlertToast/AlertToast.swift @@ -106,6 +106,9 @@ public struct AlertToast: View{ ///Banner from the bottom of the view case banner(_ transition: BannerAnimation) + + ///Present a fully custom view at the center of the screen + case custom } /// Determine what the alert will display @@ -217,7 +220,10 @@ public struct AlertToast: View{ ///Customize your alert appearance public var style: AlertStyle? = nil - + + ///The custom content shown when `displayMode` is `.custom` + public var customView: (() -> AnyView)? = nil + ///Full init public init(displayMode: DisplayMode = .alert, type: AlertType, @@ -241,7 +247,16 @@ public struct AlertToast: View{ self.type = type self.title = title } - + + ///Init for a fully custom view, presented centered like `.alert` + public init(@ViewBuilder customView: @escaping () -> Content, + style: AlertStyle? = nil){ + self.displayMode = .custom + self.type = .regular + self.style = style + self.customView = { AnyView(customView()) } + } + ///Banner from the bottom of the view public var banner: some View{ VStack{ @@ -410,7 +425,21 @@ public struct AlertToast: View{ .alertBackground(style?.backgroundColor ?? nil, useGlassEffect: style?.useGlassEffect ?? true) .cornerRadius(10) } - + + ///Fully custom content provided via the `customView` initializer + public var custom: some View{ + Group{ + if let customView = customView { + customView() + } else { + EmptyView() + } + } + .padding() + .alertBackground(style?.backgroundColor ?? nil, useGlassEffect: style?.useGlassEffect ?? true) + .cornerRadius(10) + } + ///Body init determine by `displayMode` public var body: some View{ switch displayMode{ @@ -420,6 +449,8 @@ public struct AlertToast: View{ hud case .banner: banner + case .custom: + custom } } } @@ -472,7 +503,7 @@ public struct AlertToastModifier: ViewModifier{ if isPresenting{ switch alert().displayMode{ - case .alert: + case .alert, .custom: alert() .onTapGesture { onTap?() @@ -582,7 +613,7 @@ public struct AlertToastModifier: ViewModifier{ onAppearAction() } }) - case .alert: + case .alert, .custom: content .overlay(ZStack{ main() diff --git a/Tests/AlertToastTests/AlertToastTests.swift b/Tests/AlertToastTests/AlertToastTests.swift index 0eb7cc9..b50bbba 100644 --- a/Tests/AlertToastTests/AlertToastTests.swift +++ b/Tests/AlertToastTests/AlertToastTests.swift @@ -1,4 +1,5 @@ import XCTest +import SwiftUI @testable import AlertToast final class AlertToastTests: XCTestCase { @@ -10,7 +11,16 @@ final class AlertToastTests: XCTestCase { XCTAssertEqual(toast.subTitle, "Subtitle") } + func testCustomViewInit() { + let toast = AlertToast { + Text("Custom") + } + XCTAssertEqual(toast.displayMode, .custom) + XCTAssertNotNil(toast.customView) + } + static var allTests = [ ("testInit", testInit), + ("testCustomViewInit", testCustomViewInit), ] }