Skip to content

Commit 8b8b9b3

Browse files
authored
Create toast.swift
1 parent 134471c commit 8b8b9b3

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed

toast.swift

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//
2+
// ContentView.swift
3+
// Example
4+
//
5+
// Created by Alex Trott on 20/12/2022.
6+
//
7+
8+
import SwiftUI
9+
10+
struct ContentView: View {
11+
@State private var toast: FancyToast? = nil
12+
13+
var body: some View {
14+
VStack {
15+
Button {
16+
toast = FancyToast(type: .info, title: "Toast info", message: "Toast message")
17+
} label: {
18+
Text("Run")
19+
}
20+
21+
}
22+
.toastView(toast: $toast)
23+
}
24+
}
25+
/////////////////
26+
27+
struct FancyToastView: View {
28+
var type: FancyToastStyle
29+
var title: String
30+
var message: String
31+
var onCancelTapped: (() -> Void)
32+
var body: some View {
33+
VStack(alignment: .leading) {
34+
HStack(alignment: .top) {
35+
Image(systemName: type.iconFileName)
36+
.foregroundColor(type.themeColor)
37+
38+
VStack(alignment: .leading) {
39+
Text(title)
40+
.font(.system(size: 14, weight: .semibold))
41+
42+
Text(message)
43+
.font(.system(size: 12))
44+
.foregroundColor(Color.black.opacity(0.6))
45+
}
46+
47+
Spacer(minLength: 10)
48+
49+
Button {
50+
onCancelTapped()
51+
} label: {
52+
Image(systemName: "xmark")
53+
.foregroundColor(Color.black)
54+
}
55+
}
56+
.padding()
57+
}
58+
.background(Color.white)
59+
.overlay(
60+
Rectangle()
61+
.fill(type.themeColor)
62+
.frame(width: 6)
63+
.clipped()
64+
, alignment: .leading
65+
)
66+
.frame(minWidth: 0, maxWidth: .infinity)
67+
.cornerRadius(8)
68+
.shadow(color: Color.black.opacity(0.25), radius: 4, x: 0, y: 1)
69+
.padding(.horizontal, 16)
70+
}
71+
}
72+
73+
/////////////////
74+
75+
struct FancyToast: Equatable {
76+
var type: FancyToastStyle
77+
var title: String
78+
var message: String
79+
var duration: Double = 3
80+
}
81+
82+
enum FancyToastStyle {
83+
case error
84+
case warning
85+
case success
86+
case info
87+
}
88+
89+
extension FancyToastStyle {
90+
var themeColor: Color {
91+
switch self {
92+
case .error: return Color.red
93+
case .warning: return Color.orange
94+
case .info: return Color.blue
95+
case .success: return Color.green
96+
}
97+
}
98+
99+
var iconFileName: String {
100+
switch self {
101+
case .info: return "info.circle.fill"
102+
case .warning: return "exclamationmark.triangle.fill"
103+
case .success: return "checkmark.circle.fill"
104+
case .error: return "xmark.circle.fill"
105+
}
106+
}
107+
}
108+
109+
/////////////////
110+
111+
struct FancyToastModifier: ViewModifier {
112+
@Binding var toast: FancyToast?
113+
@State private var workItem: DispatchWorkItem?
114+
115+
func body(content: Content) -> some View {
116+
content
117+
.frame(maxWidth: .infinity, maxHeight: .infinity)
118+
.overlay(
119+
ZStack {
120+
mainToastView()
121+
.offset(y: -30)
122+
}.animation(.spring(), value: toast)
123+
)
124+
.onChange(of: toast) { value in
125+
showToast()
126+
}
127+
}
128+
129+
@ViewBuilder func mainToastView() -> some View {
130+
if let toast = toast {
131+
VStack {
132+
Spacer()
133+
FancyToastView(
134+
type: toast.type,
135+
title: toast.title,
136+
message: toast.message) {
137+
dismissToast()
138+
}
139+
}
140+
.transition(.move(edge: .bottom))
141+
}
142+
}
143+
144+
private func showToast() {
145+
guard let toast = toast else { return }
146+
147+
UIImpactFeedbackGenerator(style: .light).impactOccurred()
148+
149+
if toast.duration > 0 {
150+
workItem?.cancel()
151+
152+
let task = DispatchWorkItem {
153+
dismissToast()
154+
}
155+
156+
workItem = task
157+
DispatchQueue.main.asyncAfter(deadline: .now() + toast.duration, execute: task)
158+
}
159+
}
160+
161+
private func dismissToast() {
162+
withAnimation {
163+
toast = nil
164+
}
165+
166+
workItem?.cancel()
167+
workItem = nil
168+
}
169+
}
170+
171+
extension View {
172+
func toastView(toast: Binding<FancyToast?>) -> some View {
173+
self.modifier(FancyToastModifier(toast: toast))
174+
}
175+
}

0 commit comments

Comments
 (0)