├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── macintoshi.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
│ └── xcuserdata
│ └── macintoshi.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── Sources
└── ToastKit
│ ├── ToastModifier.swift
│ ├── ToastStack
│ ├── ToastItemModel.swift
│ ├── ToastStackManager.swift
│ └── ToastStackView.swift
│ ├── Enums
│ └── ToastEnums.swift
│ ├── ToastKit.swift
│ └── Extensions
│ └── Extension+View.swift
├── Package.swift
├── LICENSE
├── Tests
└── ToastKitTests
│ ├── EnumsTests.swift
│ └── ToastStackTests.swift
└── README.md
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcuserdata/macintoshi.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Desp0o/ToastKit/HEAD/.swiftpm/xcode/package.xcworkspace/xcuserdata/macintoshi.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/Sources/ToastKit/ToastModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToastModifier.swift
3 | // ToastKit
4 | //
5 | // Created by Despo on 16.04.25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @available(macOS 14.0, *)
11 | @available(iOS 17.0, *)
12 | public struct ToastModifier: ViewModifier {
13 | @Binding var isVisible: Bool
14 | let toast: CustomToast
15 |
16 | public func body(content: Content) -> some View {
17 | content
18 | .overlay {
19 | toast
20 | }
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/Sources/ToastKit/ToastStack/ToastItemModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToastItemModel.swift
3 | // ToastKit
4 | //
5 | // Created by Despo on 18.04.25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @available(macOS 14.0, *)
11 | @available(iOS 17, *)
12 | public struct ToastItemModel: Identifiable, Equatable {
13 | public let id = UUID()
14 | let title: String
15 | let toastColor: ToastColorTypes
16 | let autoDisappearDuration: TimeInterval
17 | let isStackMaxHeight: Bool = false
18 |
19 | public static func == (lhs: ToastItemModel, rhs: ToastItemModel) -> Bool {
20 | return lhs.id == rhs.id
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcuserdata/macintoshi.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | ToastKit.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | ToastKit
16 |
17 | primary
18 |
19 |
20 | ToastKitTests
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 6.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "ToastKit",
8 | products: [
9 | // Products define the executables and libraries a package produces, making them visible to other packages.
10 | .library(
11 | name: "ToastKit",
12 | targets: ["ToastKit"]),
13 | ],
14 | targets: [
15 | // Targets are the basic building blocks of a package, defining a module or a test suite.
16 | // Targets can depend on other targets in this package and products from dependencies.
17 | .target(
18 | name: "ToastKit"),
19 | .testTarget(
20 | name: "ToastKitTests",
21 | dependencies: ["ToastKit"]
22 | ),
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/Sources/ToastKit/ToastStack/ToastStackManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToastStackManager.swift
3 | // ToastKit
4 | //
5 | // Created by Despo on 18.04.25.
6 | //
7 | import SwiftUI
8 |
9 | @available(macOS 14.0, *)
10 | @available(iOS 17, *)
11 |
12 | public class ToastStackManager: ObservableObject {
13 | @Published var toasts: [ToastItemModel] = []
14 |
15 | public init() { }
16 |
17 | @MainActor public func show(title: String, toastColor: ToastColorTypes, autoDisappearDuration: TimeInterval = 2.0) {
18 | let toast = ToastItemModel(
19 | title: title,
20 | toastColor: toastColor,
21 | autoDisappearDuration: autoDisappearDuration
22 | )
23 |
24 | toasts.insert(toast, at: 0)
25 |
26 | Task {
27 | try await Task.sleep(nanoseconds: UInt64(autoDisappearDuration * 1_000_000_000))
28 | self.removeToast(toast)
29 | }
30 | }
31 |
32 | func removeToast(_ toast: ToastItemModel) {
33 | toasts.removeAll { $0.id == toast.id }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Tornike Despotashvili
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Tests/ToastKitTests/EnumsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EnumsTests.swift
3 | // ToastKit
4 | //
5 | // Created by Despo on 18.04.25.
6 | //
7 |
8 | import XCTest
9 | @testable import ToastKit
10 |
11 | final class ToastColorTypesTests: XCTestCase {
12 | func testSuccessType() {
13 | XCTAssertEqual(ToastColorTypes.success.value, .green)
14 | }
15 |
16 | func testWarninType() {
17 | XCTAssertEqual(ToastColorTypes.warning.value, .yellow)
18 | }
19 |
20 | func testErrorType() {
21 | XCTAssertEqual(ToastColorTypes.error.value, .red)
22 | }
23 |
24 | func testInfoType() {
25 | XCTAssertEqual(ToastColorTypes.info.value, .blue)
26 | }
27 |
28 | func testCustomType() {
29 | XCTAssertEqual(ToastColorTypes.custom(.teal).value, .teal)
30 | }
31 | }
32 |
33 | final class ToastDirectionsTests: XCTestCase {
34 | func testLeadingDirection() {
35 | XCTAssertEqual(HorizontalDirection.leading.value, .leading)
36 | }
37 |
38 | func testTrailinDirection() {
39 | XCTAssertEqual(HorizontalDirection.trailing.value, .trailing)
40 | }
41 | }
42 |
43 | final class VerticalDirectionTests: XCTestCase {
44 | func testTopDirection() {
45 | XCTAssertEqual(VerticalDirection.top.value, .top)
46 | }
47 |
48 | func testBottomDirection() {
49 | XCTAssertEqual(VerticalDirection.bottom.value, .bottom)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Tests/ToastKitTests/ToastStackTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import ToastKit
3 |
4 | final class ToastStackTests: XCTestCase {
5 | private var sut: ToastStackManager!
6 |
7 | override func setUpWithError() throws {
8 | sut = ToastStackManager()
9 | }
10 |
11 | override func tearDownWithError() throws {
12 | sut = nil
13 | }
14 |
15 | @MainActor func testAddToastToStack() throws {
16 | //Given
17 | XCTAssertTrue(sut.toasts.isEmpty)
18 |
19 | //When
20 | sut.show(title: "test", toastColor: .success, autoDisappearDuration: 2)
21 |
22 | //Then
23 | XCTAssertEqual(sut.toasts.count, 1)
24 | }
25 |
26 | @MainActor func testToastDissapearWithDuration() async throws {
27 | // Given
28 | sut.show(title: "Auto Disappear", toastColor: .info, autoDisappearDuration: 2.0)
29 | XCTAssertEqual(sut.toasts.count, 1)
30 |
31 | Task {
32 | // When
33 | try await Task.sleep(nanoseconds: 2_000_000_000)
34 |
35 | // Then
36 | XCTAssertEqual(sut.toasts.count, 0)
37 | }
38 | }
39 |
40 | func testRemoveToastFromToasts() throws {
41 | //Given
42 | let toast1 = ToastItemModel(title: "One", toastColor: .info, autoDisappearDuration: 2.0)
43 | sut.toasts = [toast1]
44 |
45 | XCTAssertEqual(sut.toasts.count, 1)
46 |
47 | //When
48 | sut.removeToast(toast1)
49 |
50 | //Then
51 | XCTAssertTrue(sut.toasts.isEmpty)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Sources/ToastKit/Enums/ToastEnums.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToastTransitionType.swift
3 | // ToastKit
4 | //
5 | // Created by Despo on 15.04.25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @available(macOS 14.0, *)
11 | @available(iOS 17, *)
12 | public enum ToastTransitionType {
13 | case fade
14 | case scale
15 | case slide
16 | case move(edge: Edge)
17 | case custom(AnyTransition)
18 | }
19 |
20 | @available(macOS 14.0, *)
21 | @available(iOS 17, *)
22 | public enum ToastColorTypes {
23 | case success
24 | case warning
25 | case error
26 | case info
27 | case custom(Color)
28 | case glass
29 |
30 | var value: Color {
31 | switch self {
32 | case .success:
33 | return .green
34 | case .warning:
35 | return .yellow
36 | case .error:
37 | return .red
38 | case .info:
39 | return .blue
40 | case .glass:
41 | return .clear
42 | case .custom(let color):
43 | return color
44 | }
45 | }
46 | }
47 |
48 | @available(macOS 14.0, *)
49 | @available(iOS 17, *)
50 | public enum HorizontalDirection {
51 | case leading
52 | case trailing
53 |
54 | var value: Edge {
55 | switch self {
56 | case .leading:
57 | return .leading
58 | case .trailing:
59 | return .trailing
60 | }
61 | }
62 | }
63 |
64 | @available(macOS 14.0, *)
65 | @available(iOS 17, *)
66 | public enum VerticalDirection {
67 | case top
68 | case bottom
69 |
70 | var value: Alignment {
71 | switch self {
72 | case .top:
73 | return .top
74 | case .bottom:
75 | return .bottom
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Sources/ToastKit/ToastStack/ToastStackView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToastStackView.swift
3 | // ToastKit
4 | //
5 | // Created by Despo on 18.04.25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @available(macOS 14.0, *)
11 | @available(iOS 17, *)
12 | public struct ToastStackView: View {
13 | @StateObject var vm: ToastStackManager
14 | let transitionType: AnyTransition
15 | let isGlass: Bool
16 | let glassColor: Color
17 |
18 | public init(
19 | vm: ToastStackManager,
20 | transitionType: AnyTransition = .move(edge: .top).combined(with: .opacity),
21 | isGlass: Bool = false,
22 | glassColor: Color = .clear
23 | ) {
24 | _vm = StateObject(wrappedValue: vm)
25 | self.transitionType = transitionType
26 | self.isGlass = isGlass
27 | self.glassColor = glassColor
28 | }
29 |
30 | public var body: some View {
31 | VStack {
32 | ForEach(vm.toasts, id: \.id) { toast in
33 | ZStack {
34 | if #available(iOS 26.0, *), isGlass {
35 | CustomToast(
36 | isVisible: .constant(true),
37 | title: toast.title,
38 | toastColor: toast.toastColor,
39 | isStackMaxHeight: toast.isStackMaxHeight,
40 | isGlass: isGlass,
41 | glassColor: glassColor
42 | )
43 | } else {
44 | CustomToast(
45 | isVisible: .constant(true),
46 | title: toast.title,
47 | toastColor: toast.toastColor,
48 | isStackMaxHeight: toast.isStackMaxHeight
49 | )
50 | }
51 | }
52 | .transition(transitionType)
53 | }
54 | }
55 | .frame(maxWidth: .infinity, maxHeight: .infinity,alignment: .top)
56 | .animation(.bouncy, value: vm.toasts)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/ToastKit/ToastKit.swift:
--------------------------------------------------------------------------------
1 | // The Swift Programming Language
2 | // https://docs.swift.org/swift-book
3 |
4 | import SwiftUI
5 |
6 | @available(macOS 14.0, *)
7 | @available(iOS 17, *)
8 | public struct CustomToast: View {
9 | @State private var disappearTask: Task<(), Never>?
10 | @Binding var isVisible: Bool
11 | let title: String
12 | let toastColor: ToastColorTypes
13 | let transitionType: ToastTransitionType
14 | let animation: Animation
15 | let autoDisappear: Bool
16 | let autoDisappearDuration: TimeInterval
17 | let maxWidth: Bool
18 |
19 | let subtitle: String
20 |
21 | let font: String
22 | let titleFontSize: CGFloat
23 | let titleFontWeight: Font.Weight
24 | let titleFontColor: Color
25 |
26 | let subtitleFontSize: CGFloat
27 | let subtitleFontWeight: Font.Weight
28 | let subtitleFontColor: Color
29 |
30 | let multilineTextAlignment: TextAlignment
31 |
32 | let innerHpadding: CGFloat
33 | let innerVpadding: CGFloat
34 | let outterHpadding: CGFloat
35 | let stackAligment: Alignment
36 | let isStackMaxHeight: Bool
37 |
38 | let cornerRadius: CGFloat
39 |
40 | let shadowColor: Color
41 | let shadowRadius: CGFloat
42 | let shadowX: CGFloat
43 | let shadowY: CGFloat
44 |
45 | let withIcon: Bool
46 | let iconName: String?
47 | let iconSize: CGFloat?
48 | let iconColor: Color?
49 |
50 | let withSfsymbol: Bool
51 | let sfSymbolName: String?
52 | let sfSymbolSize: CGFloat?
53 | let sfSymbolColor: Color?
54 |
55 | let layoutDirection: LayoutDirection
56 |
57 | let closeSFicon: String
58 | let closeSFiconSize: CGFloat
59 | let closeSFiconColor: Color
60 |
61 | let isGlass: Bool
62 | let glassColor: Color
63 |
64 | init(
65 | isVisible: Binding,
66 | title: String,
67 | toastColor: ToastColorTypes = .success,
68 | transitionType: ToastTransitionType = .move(edge: .top),
69 | animation: Animation = .snappy,
70 | autoDisappear: Bool = true,
71 | autoDisappearDuration: TimeInterval = 2.0,
72 | maxWidth: Bool = false,
73 |
74 | subtitle: String = "",
75 |
76 | font: String = "SFProDisplay",
77 | titleFontSize: CGFloat = 16,
78 | titleFontWeight: Font.Weight = .regular,
79 | titleFontColor: Color = .white,
80 |
81 | subtitleFontSize: CGFloat = 14,
82 | subtitleFontWeight: Font.Weight = .regular,
83 | subtitleFontColor: Color = .white,
84 |
85 | multilineTextAlignment: TextAlignment = .center,
86 |
87 | innerHpadding: CGFloat = 20,
88 | innerVpadding: CGFloat = 10,
89 | outterHpadding: CGFloat = 20,
90 | stackAligment: Alignment = .top,
91 | isStackMaxHeight: Bool = true,
92 |
93 | cornerRadius: CGFloat = 12,
94 |
95 | shadowColor: Color = .black.opacity(0.2),
96 | shadowRadius: CGFloat = 10,
97 | shadowX: CGFloat = 0,
98 | shadowY: CGFloat = 4,
99 |
100 | withIcon: Bool = false,
101 | iconName: String? = nil,
102 | iconSize: CGFloat? = nil,
103 | iconColor: Color? = nil,
104 |
105 | withSfsymbol: Bool = false,
106 | sfSymbolName: String? = nil,
107 | sfSymbolSize: CGFloat? = nil,
108 | sfSymbolColor: Color? = nil,
109 |
110 | layoutDirection: LayoutDirection = .leftToRight,
111 |
112 | closeSFicon: String = "x.circle",
113 | closeSFiconSize: CGFloat = 18,
114 | closeSFiconColor: Color = .white,
115 |
116 | isGlass: Bool = false,
117 | glassColor: Color = .clear
118 | ) {
119 | _isVisible = isVisible
120 | self.title = title
121 | self.toastColor = toastColor
122 | self.transitionType = transitionType
123 | self.subtitle = subtitle
124 | self.autoDisappear = autoDisappear
125 | self.autoDisappearDuration = autoDisappearDuration
126 | self.animation = animation
127 | self.maxWidth = maxWidth
128 |
129 | self.font = font
130 | self.titleFontSize = titleFontSize
131 | self.titleFontWeight = titleFontWeight
132 | self.titleFontColor = titleFontColor
133 |
134 | self.subtitleFontSize = subtitleFontSize
135 | self.subtitleFontWeight = subtitleFontWeight
136 | self.subtitleFontColor = subtitleFontColor
137 |
138 | self.multilineTextAlignment = multilineTextAlignment
139 |
140 | self.innerHpadding = innerHpadding
141 | self.innerVpadding = innerVpadding
142 | self.outterHpadding = outterHpadding
143 | self.stackAligment = stackAligment
144 | self.isStackMaxHeight = isStackMaxHeight
145 |
146 | self.cornerRadius = cornerRadius
147 |
148 | self.shadowColor = shadowColor
149 | self.shadowRadius = shadowRadius
150 | self.shadowX = shadowX
151 | self.shadowY = shadowY
152 |
153 | self.withIcon = withIcon
154 | self.iconName = iconName
155 | self.iconSize = iconSize
156 | self.iconColor = iconColor
157 |
158 | self.withSfsymbol = withSfsymbol
159 | self.sfSymbolName = sfSymbolName
160 | self.sfSymbolSize = sfSymbolSize
161 | self.sfSymbolColor = sfSymbolColor
162 |
163 | self.layoutDirection = layoutDirection
164 |
165 | self.closeSFicon = closeSFicon
166 | self.closeSFiconSize = closeSFiconSize
167 | self.closeSFiconColor = closeSFiconColor
168 |
169 | self.isGlass = isGlass
170 | self.glassColor = glassColor
171 | }
172 |
173 | public var body: some View {
174 | ZStack(alignment: stackAligment) {
175 | if isVisible {
176 | HStack {
177 | if !withIcon && !withSfsymbol {
178 | VStack {
179 | Text(title)
180 | .font(.custom(font, size: titleFontSize))
181 | .font(.system(size: titleFontSize))
182 | .fontWeight(titleFontWeight)
183 | .foregroundStyle(titleFontColor)
184 | .multilineTextAlignment(multilineTextAlignment)
185 |
186 | if !subtitle.isEmpty {
187 | Text(subtitle)
188 | .font(.custom(font, size: subtitleFontSize))
189 | .fontWeight(subtitleFontWeight)
190 | .foregroundStyle(subtitleFontColor)
191 | .multilineTextAlignment(multilineTextAlignment)
192 | }
193 | }
194 | } else {
195 | HStack(spacing: 20) {
196 | if withSfsymbol {
197 | Image(systemName: sfSymbolName ?? "")
198 | .renderingMode(.template)
199 | .resizable()
200 | .scaledToFit()
201 | .frame(width: sfSymbolSize, height: sfSymbolSize)
202 | .foregroundStyle(sfSymbolColor ?? .clear)
203 | } else {
204 | Image(iconName ?? "")
205 | .resizable()
206 | .renderingMode(iconColor != nil ? .template : .original)
207 | .scaledToFit()
208 | .foregroundStyle(iconColor ?? .clear)
209 | .frame(width: iconSize, height: iconSize)
210 | }
211 |
212 | Text(title)
213 | .font(.custom(font, size: titleFontSize))
214 | .fontWeight(titleFontWeight)
215 | .foregroundStyle(titleFontColor)
216 | .multilineTextAlignment(multilineTextAlignment)
217 | }
218 | .environment(\.layoutDirection, layoutDirection)
219 | }
220 | }
221 | .padding(.horizontal, innerHpadding)
222 | .padding(.vertical, innerVpadding)
223 | .if(maxWidth) { $0.frame(maxWidth: .infinity)}
224 | .background(toastColor.value)
225 | .clipShape(RoundedRectangle(cornerRadius: cornerRadius))
226 | .transition(transition(for: transitionType))
227 | .shadow(color: shadowColor, radius: shadowRadius, x: shadowX, y: shadowY)
228 | .overlay {
229 | if !autoDisappear {
230 | ZStack {
231 | Button {
232 | isVisible = false
233 | } label: {
234 | Image(systemName: closeSFicon)
235 | .renderingMode(.template)
236 | .resizable()
237 | .scaledToFit()
238 | .frame(width: closeSFiconSize, height: closeSFiconSize)
239 | .foregroundStyle(closeSFiconColor)
240 | }
241 | .offset(x: -7, y: 10)
242 | }
243 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
244 | }
245 | }
246 | .padding(.horizontal, outterHpadding)
247 | .background {
248 | if #available(iOS 26.0, *), #available(macOS 26.0, *), isGlass {
249 | Color.clear.glassEffect(.regular.tint(glassColor))
250 | }
251 | }
252 | }
253 | }
254 | .frame(maxWidth: .infinity, alignment: stackAligment)
255 | .if(isStackMaxHeight) { $0.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: stackAligment)}
256 | .onChange(of: isVisible) { _, newValue in
257 | if newValue {
258 | disappearTask?.cancel()
259 | if autoDisappear {
260 | disappearTask = Task {
261 | try? await Task.sleep(nanoseconds: UInt64(autoDisappearDuration * 1_000_000_000))
262 | if !Task.isCancelled {
263 | isVisible = false
264 | }
265 | }
266 | }
267 | } else {
268 | disappearTask?.cancel()
269 | disappearTask = nil
270 | }
271 | }
272 | .animation(animation, value: isVisible)
273 | }
274 |
275 | func transition(for type: ToastTransitionType) -> AnyTransition {
276 | switch type {
277 | case .fade:
278 | return .opacity
279 | case .scale:
280 | return .scale
281 | case .slide:
282 | return .slide
283 | case .move(let edge):
284 | return .move(edge: edge).combined(with: .opacity)
285 | case .custom(let transition):
286 | return transition
287 | }
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/Sources/ToastKit/Extensions/Extension+View.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // ToastKit
4 | //
5 | // Created by Despo on 15.04.25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @available(macOS 14.0, *)
11 | @available(iOS 17.0, *)
12 |
13 | public extension View {
14 | @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View {
15 | if condition {
16 | transform(self)
17 | } else {
18 | self
19 | }
20 | }
21 | }
22 |
23 | @available(macOS 14.0, *)
24 | @available(iOS 17, *)
25 | public extension View {
26 | func toast(
27 | isVisible: Binding,
28 | title: String,
29 | toastColor: ToastColorTypes = .success,
30 | transitionType: ToastTransitionType = .move(edge: .top),
31 | animation: Animation = .snappy,
32 | autoDisappear: Bool = true,
33 | autoDisappearDuration: TimeInterval = 2.0,
34 | maxWidth: Bool = false,
35 | subtitle: String = "",
36 | font: String = "SFProDisplay",
37 | titleFontSize: CGFloat = 16,
38 | titleFontWeight: Font.Weight = .semibold,
39 | titleFontColor: Color = .white,
40 | subtitleFontSize: CGFloat = 14,
41 | subtitleFontWeight: Font.Weight = .regular,
42 | subtitleFontColor: Color = .white,
43 | multilineTextAlignment: TextAlignment = .center,
44 | innerHpadding: CGFloat = 30,
45 | innerVpadding: CGFloat = 10,
46 | outterHpadding: CGFloat = 20,
47 | stackAligment: Alignment = .top,
48 | cornerRadius: CGFloat = 12,
49 | shadowColor: Color = .black.opacity(0.2),
50 | shadowRadius: CGFloat = 10,
51 | shadowX: CGFloat = 0,
52 | shadowY: CGFloat = 4,
53 | withIcon: Bool = false,
54 | iconName: String? = nil,
55 | iconSize: CGFloat? = nil,
56 | iconColor: Color? = nil,
57 | withSfsymbol: Bool = false,
58 | sfSymbolName: String? = "x.circle",
59 | sfSymbolSize: CGFloat? = 18,
60 | sfSymbolColor: Color? = .white,
61 | layoutDirection: LayoutDirection = .leftToRight,
62 | closeSFicon: String = "x.circle",
63 | closeSFiconSize: CGFloat = 18,
64 | closeSFiconColor: Color = .white,
65 | isGlass: Bool = false,
66 | glassColor: Color = .clear
67 | ) -> some View {
68 | modifier(
69 | ToastModifier(
70 | isVisible: isVisible,
71 | toast: CustomToast(
72 | isVisible: isVisible,
73 | title: title,
74 | toastColor: toastColor,
75 | transitionType: transitionType,
76 | animation: animation,
77 | autoDisappear: autoDisappear,
78 | autoDisappearDuration: autoDisappearDuration,
79 | maxWidth: maxWidth,
80 | subtitle: subtitle,
81 | font: font,
82 | titleFontSize: titleFontSize,
83 | titleFontWeight: titleFontWeight,
84 | titleFontColor: titleFontColor,
85 | subtitleFontSize: subtitleFontSize,
86 | subtitleFontWeight: subtitleFontWeight,
87 | subtitleFontColor: subtitleFontColor,
88 | multilineTextAlignment: multilineTextAlignment,
89 | innerHpadding: innerHpadding,
90 | innerVpadding: innerVpadding,
91 | outterHpadding: outterHpadding,
92 | stackAligment: stackAligment,
93 | cornerRadius: cornerRadius,
94 | shadowColor: shadowColor,
95 | shadowRadius: shadowRadius,
96 | shadowX: shadowX,
97 | shadowY: shadowY,
98 | withIcon: withIcon,
99 | iconName: iconName,
100 | iconSize: iconSize,
101 | iconColor: iconColor,
102 | withSfsymbol: withSfsymbol,
103 | sfSymbolName: sfSymbolName,
104 | sfSymbolSize: sfSymbolSize,
105 | sfSymbolColor: sfSymbolColor,
106 | layoutDirection: layoutDirection,
107 | closeSFicon: closeSFicon,
108 | closeSFiconSize: closeSFiconSize,
109 | closeSFiconColor: closeSFiconColor,
110 | isGlass: isGlass,
111 | glassColor: glassColor
112 | )
113 | )
114 | )
115 | }
116 | }
117 |
118 |
119 | @available(macOS 14.0, *)
120 | @available(iOS 17, *)
121 | public extension View {
122 | func successToast(
123 | isVisible: Binding,
124 | title: String,
125 | toastColor: ToastColorTypes = .success,
126 | animation: Animation = .snappy,
127 | titleFontColor: Color = .white,
128 | maxWidth: Bool = false
129 | ) -> some View {
130 | toast(
131 | isVisible: isVisible,
132 | title: title,
133 | toastColor: toastColor,
134 | animation: animation,
135 | maxWidth: maxWidth,
136 | titleFontColor: titleFontColor
137 | )
138 | }
139 | }
140 |
141 |
142 | @available(macOS 14.0, *)
143 | @available(iOS 17, *)
144 | public extension View {
145 | func warningToast(
146 | isVisible: Binding,
147 | title: String,
148 | toastColor: ToastColorTypes = .warning,
149 | animation: Animation = .snappy,
150 | titleFontColor: Color = .white,
151 | maxWidth: Bool = false
152 | ) -> some View {
153 | toast(
154 | isVisible: isVisible,
155 | title: title,
156 | toastColor: toastColor,
157 | animation: animation,
158 | maxWidth: maxWidth,
159 | titleFontColor: titleFontColor
160 | )
161 | }
162 | }
163 |
164 |
165 | @available(macOS 14.0, *)
166 | @available(iOS 17, *)
167 | public extension View {
168 | func errorToast(
169 | isVisible: Binding,
170 | title: String,
171 | toastColor: ToastColorTypes = .error,
172 | animation: Animation = .snappy,
173 | titleFontColor: Color = .white,
174 | maxWidth: Bool = false
175 | ) -> some View {
176 | toast(
177 | isVisible: isVisible,
178 | title: title,
179 | toastColor: toastColor,
180 | animation: animation,
181 | maxWidth: maxWidth,
182 | titleFontColor: titleFontColor
183 | )
184 | }
185 | }
186 |
187 |
188 | @available(macOS 14.0, *)
189 | @available(iOS 17, *)
190 | public extension View {
191 | func bottomToast(
192 | isVisible: Binding,
193 | title: String,
194 | toastColor: ToastColorTypes = .success,
195 | animation: Animation = .snappy,
196 | titleFontColor: Color = .white,
197 | maxWidth: Bool = false
198 | ) -> some View {
199 | toast(
200 | isVisible: isVisible,
201 | title: title,
202 | toastColor: toastColor,
203 | transitionType: .move(edge: .bottom),
204 | animation: animation,
205 | maxWidth: maxWidth,
206 | titleFontColor: titleFontColor,
207 | stackAligment: .bottom
208 | )
209 | }
210 | }
211 |
212 |
213 | @available(macOS 14.0, *)
214 | @available(iOS 17, *)
215 | public extension View {
216 | func edgeSlideToast(
217 | isVisible: Binding,
218 | title: String,
219 | toastColor: ToastColorTypes = .success,
220 | animation: Animation = .snappy,
221 | hDirection: HorizontalDirection = .trailing,
222 | vDirection: VerticalDirection = .top,
223 | titleFontColor: Color = .white,
224 | maxWidth: Bool = false
225 | ) -> some View {
226 | toast(
227 | isVisible: isVisible,
228 | title: title,
229 | toastColor: toastColor,
230 | transitionType: .move(edge: hDirection.value),
231 | animation: animation,
232 | maxWidth: maxWidth,
233 | titleFontColor: titleFontColor,
234 | stackAligment: vDirection.value
235 | )
236 | }
237 | }
238 |
239 |
240 | @available(macOS 14.0, *)
241 | @available(iOS 17, *)
242 | public extension View {
243 | func infoToast(
244 | isVisible: Binding,
245 | title: String,
246 | toastColor: ToastColorTypes = .info,
247 | animation: Animation = .snappy,
248 | titleFontColor: Color = .white,
249 | maxWidth: Bool = false
250 | ) -> some View {
251 | toast(
252 | isVisible: isVisible,
253 | title: title,
254 | toastColor: .info,
255 | animation: animation,
256 | maxWidth: maxWidth,
257 | titleFontColor: titleFontColor
258 | )
259 | }
260 | }
261 |
262 |
263 | @available(macOS 14.0, *)
264 | @available(iOS 17, *)
265 | public extension View {
266 | func toastWithIcon(
267 | isVisible: Binding,
268 | title: String,
269 | toastColor: ToastColorTypes = .success,
270 | iconName: String?,
271 | iconSize: CGFloat? = 24,
272 | iconColor: Color? = nil,
273 | transitionType: ToastTransitionType = .move(edge: .top),
274 | animation: Animation = .snappy,
275 | vDirection: VerticalDirection = .top,
276 | titleFontColor: Color = .white,
277 | maxWidth: Bool = false
278 | ) -> some View {
279 | toast(
280 | isVisible: isVisible,
281 | title: title,
282 | toastColor: toastColor,
283 | transitionType: transitionType,
284 | animation: animation,
285 | maxWidth: maxWidth,
286 | titleFontColor: titleFontColor,
287 | stackAligment: vDirection.value,
288 | withIcon: true,
289 | iconName: iconName,
290 | iconSize: iconSize,
291 | iconColor: iconColor,
292 | )
293 | }
294 | }
295 |
296 |
297 | @available(macOS 14.0, *)
298 | @available(iOS 17, *)
299 | public extension View {
300 | func toastWithSFSymbol(
301 | isVisible: Binding,
302 | title: String,
303 | toastColor: ToastColorTypes = .success,
304 | titleFontColor: Color = .white,
305 | sfSymbolName: String?,
306 | sfSymbolSize: CGFloat? = 24,
307 | sfSymbolColor: Color? = .white,
308 | transitionType: ToastTransitionType = .move(edge: .top),
309 | animation: Animation = .snappy,
310 | vDirection: VerticalDirection = .top,
311 | maxWidth: Bool = false,
312 | layoutDirection: LayoutDirection = .leftToRight
313 | ) -> some View {
314 | toast(
315 | isVisible: isVisible,
316 | title: title,
317 | toastColor: toastColor,
318 | transitionType: transitionType,
319 | animation: animation,
320 | maxWidth: maxWidth,
321 | titleFontColor: titleFontColor,
322 | stackAligment: vDirection.value,
323 | withSfsymbol: true,
324 | sfSymbolName: sfSymbolName,
325 | sfSymbolSize: sfSymbolSize,
326 | sfSymbolColor: sfSymbolColor,
327 | layoutDirection: layoutDirection
328 | )
329 | }
330 | }
331 |
332 |
333 | @available(macOS 26.0, *)
334 | @available(iOS 26, *)
335 | public extension View {
336 | func glassToast(
337 | isVisible: Binding,
338 | title: String,
339 | subtitle: String = "",
340 | glassColor: Color = .clear,
341 | titleFontColor: Color = .white,
342 | subtitleFontColor: Color = .white,
343 | maxWidth: Bool = false,
344 | transitionType: ToastTransitionType = .move(edge: .top),
345 | animation: Animation = .snappy,
346 | vDirection: VerticalDirection = .top,
347 | layoutDirection: LayoutDirection = .leftToRight
348 | ) -> some View {
349 | modifier(
350 | ToastModifier(
351 | isVisible: isVisible,
352 | toast: CustomToast(
353 | isVisible: isVisible,
354 | title: title,
355 | toastColor: .glass,
356 | transitionType: transitionType,
357 | animation: animation,
358 | maxWidth: maxWidth,
359 | subtitle: subtitle,
360 | titleFontColor: titleFontColor,
361 | subtitleFontColor: subtitleFontColor,
362 | stackAligment: vDirection.value,
363 | layoutDirection: layoutDirection, isGlass: true,
364 | glassColor: glassColor
365 | )
366 | )
367 | )
368 | }
369 | }
370 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 |
4 | # ToastKit
5 | ToastKit is a lightweight and fully customizable Swift package that helps you display informative toast messages in your app. It’s easy to use, supports
6 | various built-in toast styles like success, warning, info, error, with icons.... and also allows full customization for your specific needs.
7 |
8 | You can quickly use ready-made toasts or create your own custom toast view with complete control over layout, colors, animations, icons, and more.
9 |
10 |
11 |     
12 |
13 |
14 |
15 | ## Features 🚀
16 | - Full Customization
17 | - Glass Effect
18 | - Max Width Support
19 | - Custom Icons & SF Symbols
20 | - Auto Dismiss
21 | - Transition Types
22 | - Flexible Layout Direction
23 | - Text Styling Options
24 | - Shadow Customization
25 | - Corner Radius Control
26 | - Optional Subtitle
27 | - Adaptive Stack Alignment
28 | - Smooth Animations
29 | - Manual Close Button
30 | - Responsive Design
31 |
32 | ---------
33 | ### GlassEffect
34 |
35 | 
36 |
37 | ```swift
38 | VStack {
39 |
40 | }
41 | .frame(maxWidth: .infinity, maxHeight: .infinity)
42 | //simple usage
43 | .glassToast(isVisible: $isVisible, title: title)
44 |
45 | //with full parameters
46 | .glassToast(
47 | isVisible: $isVisible2,
48 | title: title,
49 | subtitle: "if you have iOS 26 you can use this toast",
50 | glassColor: .red.opacity(0.5),
51 | titleFontColor: .black,
52 | subtitleFontColor: .black,
53 | maxWidth: false,
54 | transitionType: .custom(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)).combined(with: .opacity)),
55 | animation: .smooth,
56 | vDirection: .bottom
57 | )
58 | ```
59 |
60 | ##### Configuration ⚙️
61 | | Parameter | Type | Default Value | Description |
62 | |-------------------|------------------------------|--------------------------------|-------------|
63 | | `isVisible` | Binding | — | Binding to control visibility. |
64 | | `title` | String | — | The main message displayed in the toast. |
65 | | `subtitle` | String | `""` | Subtitle text for additional info. |
66 | | `titleFontColor` | Color | `.white` | Font color of the title. |
67 | | `subtitleFontColor` | Color | `.white` | Font color of the subtitle. |
68 | | `transitionType` | ToastTransitionType | .move(edge: .top) | The transition animation for how the toast appears/disappears. |
69 | | `animation` | Animation | .snappy | Animation used to present and dismiss the toast. |
70 | | `vDirection` | VerticalDirection | .top | Vertical position of the toast (`.top` or `.bottom`). |
71 | | `maxWidth` | Bool | false | If `true`, toast takes maximum available width. |
72 |
73 | ### Success / Warning / Error/ Info - Toasts
74 | 
75 |
76 | ##### simple toast
77 | ```swift
78 | VStack {
79 |
80 | }
81 | .frame(maxWidth: .infinity, maxHeight: .infinity)
82 | .successToast(isVisible: $isVisible, title: "Success")
83 | .warningToast(isVisible: $isVisible, title: "Warning")
84 | .errorToast(isVisible: $isVisible, title: "Error")
85 | .infoToast(isVisible: $isVisible, title: "Info")
86 | ```
87 |
88 | ##### with full parameters
89 | ```swift
90 | VStack {
91 |
92 | }
93 | .successToast(isVisible: $isVisible, title: "success full width", toastColor: .success, animation: .snappy, titleFontColor: .white, maxWidth: false)
94 | .warningToast(isVisible: $isVisible, title: "warning full width", toastColor: .warning, animation: .snappy, titleFontColor: .white, maxWidth: false)
95 | .errorToast(isVisible: $isVisible, title: "error full width", toastColor: .error, animation: .snappy, titleFontColor: .white, maxWidth: false)
96 | .infoToast(isVisible: $isVisible, title: "info full width", toastColor: .info, animation: .snappy, titleFontColor: .white, maxWidth: false)
97 | ```
98 |
99 | ##### Configuration ⚙️
100 | | Parameter | Type | Default Value | Description |
101 | | :----------------------| :--------------------- | :---------------------- | :---------------------------------- |
102 | | `isVisible ` | Binding | — | Binding to control visibility. |
103 | | `title` | String | — | The main message displayed in the toast. |
104 | | `toastColor` | ToastColorTypes | .success / .warning / .error / .info | The visual style or color theme of the toast . |
105 | | `animation` | Animation | .snappy | Animation used to present and dismiss the toast. |
106 | | `titleFontColor` | Color | .white | The color of the toast message text. |
107 | | `maxWidth` | Bool | false | Whether the toast should stretch to the maximum width. |
108 |
109 | ---------
110 | ### Bottom - Toasts
111 | 
112 |
113 | ##### simple toast
114 | ```swift
115 | VStack {
116 |
117 | }
118 | .bottomToast(isVisible: $isVisible, title: "bottom")
119 | ```
120 |
121 | ##### with full parameters
122 | ```swift
123 | VStack {
124 |
125 | }
126 | .bottomToast(
127 | isVisible: $isVisible,
128 | title: "bottom",
129 | toastColor: .custom(.indigo),
130 | animation: .bouncy,
131 | titleFontColor: .white,
132 | maxWidth: false
133 | )
134 | ```
135 |
136 | ##### Configuration ⚙️
137 |
138 | | Parameter | Type | Default Value | Description |
139 | |------------------|-------------------|----------------|-------------|
140 | | `isVisible` | Binding | — | Binding to control visibility. |
141 | | `title` | String | — | The main message displayed in the toast. |
142 | | `toastColor` | ToastColorTypes | .success | The color type or style of the toast (`.success`, `.error`, `.info`, `.warning`, `.custom(Color)`). |
143 | | `animation` | Animation | .snappy | Animation used to present and dismiss the toast. |
144 | | `titleFontColor` | Color | .white | The color of the toast message text. |
145 | | `maxWidth` | Bool | false | If true, toast stretches to maximum available width. |
146 | ----------------
147 |
148 |
149 | ### Edge Slide Toast - Toasts
150 | 
151 |
152 | ##### simple toast
153 | ```swift
154 | VStack {
155 |
156 | }
157 | .edgeSlideToast(isVisible: $isVisible, title: "slide")
158 | ```
159 | ##### with full parameters
160 | ```swift
161 | VStack {
162 |
163 | }
164 | .edgeSlideToast(
165 | isVisible: $isVisible,
166 | title: "slide",
167 | toastColor: .info,
168 | animation: .bouncy,
169 | hDirection: .leading,
170 | vDirection: .top,
171 | titleFontColor: .white,
172 | maxWidth: false
173 | )
174 | ```
175 | ##### Configuration ⚙️
176 |
177 | | Parameter | Type | Default Value | Description |
178 | |------------------|-----------------------|----------------|-------------|
179 | | `isVisible` | Binding | — | Binding to control visibility. |
180 | | `title` | String | — | The main message displayed in the toast. |
181 | | `toastColor` | ToastColorTypes | .success | The color type or style of the toast (`.success`, `.error`, `.info`, `.warning`, `.custom(Color)`). |
182 | | `animation` | Animation | .snappy | Animation used to present and dismiss the toast. |
183 | | `hDirection` | HorizontalDirection | .trailing | Slide from horizontal edge (`.leading` or `.trailing`). |
184 | | `vDirection` | VerticalDirection | .top | Vertical position of the toast (`.top` or `.bottom`). |
185 | | `titleFontColor` | Color | .white | The color of the toast message text. |
186 | | `maxWidth` | Bool | false | If `true`, toast stretches to maximum available width. |
187 | ----------------
188 |
189 | ### Toast with SF Symbol
190 | 
191 |
192 | ##### simple toast
193 | ```swift
194 | VStack {
195 |
196 | }
197 | .toastWithSFSymbol(
198 | isVisible: $isVisivble,
199 | title: "Toast with SF symbol",
200 | sfSymbolName: "sun.max"
201 | )
202 | ```
203 | ##### with full parameters
204 | ```swift
205 | VStack {
206 |
207 | }
208 | .toastWithSFSymbol(
209 | isVisible: $isVisivble,
210 | title: "Toast with SF symbol",
211 | toastColor: .custom(.indigo),
212 | titleFontColor: .white,
213 | sfSymbolName: "sun.max",
214 | sfSymbolSize: 18,
215 | sfSymbolColor: .black,
216 | transitionType: .scale,
217 | animation: .smooth,
218 | vDirection: .top,
219 | maxWidth: false,
220 | layoutDirection: .leftToRight
221 | )
222 | ```
223 | ##### Configuration ⚙️
224 |
225 | | Parameter | Type | Default Value | Description |
226 | |-------------------|------------------------------|--------------------------------|-------------|
227 | | `isVisible` | Binding | — | Binding to control visibility. |
228 | | `title` | String | — | The main message displayed in the toast. |
229 | | `toastColor` | ToastColorTypes | .success | The color type or style of the toast (`.success`, `.error`, `.info`, `.warning`, `.custom(Color)`). |
230 | | `titleFontColor` | Color | .white | The color of the toast message text. |
231 | | `sfSymbolName` | String? | nil | Optional name of an SF Symbol. |
232 | | `sfSymbolSize` | CGFloat? | 24 | Size of the SF Symbol icon. |
233 | | `sfSymbolColor` | Color? | .white | Color of the SF Symbol icon. |
234 | | `transitionType` | ToastTransitionType | .move(edge: .top) | The transition animation for how the toast appears/disappears. |
235 | | `animation` | Animation | .snappy | Animation used to present and dismiss the toast. |
236 | | `vDirection` | VerticalDirection | .top | Vertical position of the toast (`.top` or `.bottom`). |
237 | | `maxWidth` | Bool | false | If `true`, toast takes maximum available width. |
238 | | `layoutDirection` | LayoutDirection | .leftToRight | Layout direction of content (`.leftToRight` or `.rightToLeft`). |
239 | -----------
240 |
241 | ### Toast with custom icon or image
242 | 
243 |
244 | ##### simple toast
245 | ```swift
246 | VStack {
247 |
248 | }
249 | .toastWithIcon(
250 | isVisible: $isVisivble,
251 | title: "with custom icon",
252 | iconName: "swift"
253 | )
254 | ```
255 |
256 | ##### with full parameters
257 | ```swift
258 | VStack {
259 |
260 | }
261 | .toastWithIcon(
262 | isVisible: $showWithCustomIconToast,
263 | title: "with custom icon",
264 | toastColor: .custom(.orange),
265 | iconName: "swift",
266 | iconSize: 18,
267 | iconColor: nil,
268 | transitionType: .move(edge: .top),
269 | animation: .bouncy,
270 | vDirection: .top,
271 | titleFontColor: .white,
272 | maxWidth: false,
273 | layoutDirection: .leftToRight
274 | )
275 | ```
276 | ##### Configuration ⚙️
277 |
278 | | Parameter | Type | Default Value | Description |
279 | |-------------------|------------------------------|--------------------------------|-------------|
280 | | `isVisible` | Binding | — | Binding to control visibility. |
281 | | `title` | String | — | The main message displayed in the toast. |
282 | | `toastColor` | ToastColorTypes | .success | The color type or style of the toast (`.success`, `.error`, `.info`, `.warning`, `.custom(Color)`). |
283 | | `iconName` | String? | nil | Optional name of a custom icon (from asset). |
284 | | `iconSize` | CGFloat? | 24 | Size of the custom icon. |
285 | | `iconColor` | Color? | .white | Color of the custom icon. |
286 | | `transitionType` | ToastTransitionType | .move(edge: .top) | The transition animation for how the toast appears/disappears. |
287 | | `animation` | Animation | .snappy | Animation used to present and dismiss the toast. |
288 | | `vDirection` | VerticalDirection | .top | Vertical position of the toast (`.top` or `.bottom`). |
289 | | `titleFontColor` | Color | .white | The color of the toast message text. |
290 | | `maxWidth` | Bool | false | If `true`, toast takes maximum available width. |
291 | | `layoutDirection` | LayoutDirection | .leftToRight | Layout direction of content (`.leftToRight` or `.rightToLeft`). |
292 |
293 | ------------
294 |
295 | ## 🍞 Toast Stack
296 | 
297 | 
298 |
299 | ### With ToastStackManager, you can show toasts at the same time!
300 |
301 | #### Certainly, you can utilize toast stacks from your view model. Here’s an example:
302 | ```swift
303 | // in your ViewModel
304 |
305 | import ToastKit
306 | import Combine
307 |
308 | final class ViewModel: ObservableObject {
309 | let toastManager: ToastStackManager
310 |
311 | init(toastManager: ToastStackManager = ToastStackManager()) {
312 | self.toastManager = toastManager
313 | }
314 |
315 | @MainActor func foo() {
316 | // Your logic
317 | toastManager.show(title: "foo success toast", toastColor: .success, autoDisappearDuration: 3.0)
318 |
319 | // Your logic
320 | toastManager.show(title: "foo info toast", toastColor: .info)
321 | }
322 | }
323 | ```
324 |
325 | #### in your view
326 |
327 | ```swift
328 | import ToastKit
329 | import SwiftUI
330 |
331 | struct ProfileView: View {
332 | @StateObject private var vm: ViewModel
333 |
334 | init(vm: ViewModel = ViewModel()) {
335 | _vm = StateObject(wrappedValue: vm)
336 | }
337 |
338 | var body: some View {
339 | ZStack {
340 | // Your view
341 |
342 | ToastStackView(vm: vm.toastManager)
343 | // Alternatively, you can utilize it with a custom transition.
344 | ToastStackView(vm: vm.toastManager, transitionType: .move(edge: .trailing).combined(with: .opacity))
345 | }
346 | }
347 | }
348 | ```
349 |
350 | ##### ToastStackView Configuration ⚙️
351 | | Parameter | Type | Default Value | Description |
352 | |-------------------|------------------------------|--------------------------------|-------------|
353 | | `title` | String | - | The main message displayed in the toast. |
354 | | `toastColor` | ToastColorTypes | - | The color type or style of the toast. |
355 | | `autoDisappearDuration`| TimeInterval | 2.0 | Duration before toast disappears. |
356 |
357 | ##### ToastStackManager Configuration ⚙️
358 | | Parameter | Type | Default Value | Description |
359 | |-------------------|------------------------------|--------------------------------|-------------|
360 | | `vm` | ToastStackManager | - | The view model that manages |
361 | | `transitionType`| AnyTransition | - | Transition animation for appearing/disappearing. |
362 |
363 | -----------
364 |
365 |
366 | ## ⚠️ Alternatively, you can utilize the `.toast` method to construct a fully customizable toast by specifying the following parameters:
367 |
368 | ##### Configuration ⚙️
369 | | Parameter | Type | Default Value | Description |
370 | |-------------------|------------------------------|--------------------------------|-------------|
371 | | `isVisible` | Binding | — | Binding to control visibility. |
372 | | `title` | String | — | The main toast message. |
373 | | `toastColor` | ToastColorTypes | `.success` | The color type or style of the toast. |
374 | | `transitionType` | ToastTransitionType | `.move(edge: .top)` | Transition animation for appearing/disappearing. |
375 | | `animation` | Animation | `.snappy` | Animation used to show/hide the toast. |
376 | | `autoDisappear` | Bool | `true` | If `true`, toast disappears automatically. |
377 | | `autoDisappearDuration`| TimeInterval | `2.0` | Duration before toast disappears. |
378 | | `maxWidth` | Bool | `false` | If `true`, toast takes maximum width. |
379 | | `subtitle` | String | `""` | Subtitle text for additional info. |
380 | | `font` | String | `"SFProDisplay"` | Name of the font used in text. |
381 | | `titleFontSize` | CGFloat | `16` | Font size of the title. |
382 | | `titleFontWeight` | Font.Weight | `.semibold` | Font weight of the title. |
383 | | `titleFontColor` | Color | `.white` | Font color of the title. |
384 | | `subtitleFontSize` | CGFloat | `14` | Font size of the subtitle. |
385 | | `subtitleFontWeight` | Font.Weight | `.regular` | Font weight of the subtitle. |
386 | | `subtitleFontColor` | Color | `.white` | Font color of the subtitle. |
387 | | `multilineTextAlignment`| TextAlignment | `.center` | Alignment of multiline text. |
388 | | `innerHpadding` | CGFloat | `20` | Inner horizontal padding. |
389 | | `innerVpadding` | CGFloat | `10` | Inner vertical padding. |
390 | | `outterHpadding` | CGFloat | `20` | Outer horizontal padding. |
391 | | `stackAligment` | Alignment | `.top` | Stack alignment inside the toast. |
392 | | `isStackMaxHeight` | Bool | `true` | occupies the maximum available height |
393 | | `cornerRadius` | CGFloat | `12` | Corner radius of the toast. |
394 | | `shadowColor` | Color | `.black.opacity(0.2)` | Shadow color. |
395 | | `shadowRadius` | CGFloat | `10` | Radius of the toast's shadow. |
396 | | `shadowX` | CGFloat | `0` | Horizontal offset of shadow. |
397 | | `shadowY` | CGFloat | `4` | Vertical offset of shadow. |
398 | | `withIcon` | Bool | `false` | Whether to show a custom icon. |
399 | | `iconName` | String? | `nil` | Name of the custom icon. |
400 | | `iconSize` | CGFloat? | `nil` | Size of the custom icon. |
401 | | `iconColor` | Color? | `nil` | Color of the custom icon. |
402 | | `withSfsymbol` | Bool | `false` | Whether to show an SF Symbol. |
403 | | `sfSymbolName` | String? | `nil` | Name of the SF Symbol. |
404 | | `sfSymbolSize` | CGFloat? | `nil` | Size of the SF Symbol. |
405 | | `sfSymbolColor` | Color? | `nil` | Color of the SF Symbol. |
406 | | `layoutDirection` | LayoutDirection | `.leftToRight` | Layout direction of content. |
407 | | `closeSFicon` | String | `"x.circle"` | SF Symbol used as close button. |
408 | | `closeSFiconSize` | CGFloat | `18` | Size of the close icon. |
409 | | `closeSFiconColor` | Color | `.white` | Color of the close icon. |
410 | ----
411 | ## Installation via Swift Package Manager 🖥️
412 | - Open your project.
413 | - Go to File → Add Package Dependencies.
414 | - Enter URL: https://github.com/Desp0o/ToastKit.git
415 | - Click Add Package.
416 |
417 | ## Contact 📬
418 |
419 | - Email: tornike.despotashvili@gmail.com
420 | - LinkedIn: https://www.linkedin.com/in/tornike-despotashvili-250150219/
421 | - github: https://github.com/Desp0o
422 |
423 |
424 |
--------------------------------------------------------------------------------