Today we will build a custom modifier to present toast message in SwiftUI. Out final product will look like this.
We will start with data model which will take title and image for the Toast message.
struct ToastDataModel {
var title:String
var image:String
}
Next we will build Toast view. This view will take data model and a boolean binding parameter to show/hide the toast message.
struct Toast: View {
let dataModel: ToastDataModel
@Binding var show: Bool
We will use transition for this example and you can learn more about it at this link
Here is how our Toast view will look like
struct Toast: View {
let dataModel: ToastDataModel
@Binding var show: Bool
var body: some View {
VStack {
Spacer()
HStack {
Image(systemName: dataModel.image)
Text(dataModel.title)
}.font(.headline)
.foregroundColor(.primary)
.padding([.top,.bottom],20)
.padding([.leading,.trailing],40)
.background(Color(UIColor.secondarySystemBackground))
.clipShape(Capsule())
}
.frame(width: UIScreen.main.bounds.width / 1.25)
.transition(AnyTransition.move(edge: .bottom).combined(with: .opacity))
.onTapGesture {
withAnimation {
self.show = false
}
}.onAppear(perform: {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
self.show = false
}
}
})
}
}
We will put together a custom modifier which will take a toast View and boolean binding value to show/hide the toast.
struct ToastModifier : ViewModifier {
@Binding var show: Bool
let toastView: Toast
func body(content: Content) -> some View {
ZStack {
content
if show {
toastView
}
}
}
}
Let’s extend View protocol to add a toast function to make it convenient to add this modifier to any view.
extension View {
func toast(toastView: Toast, show: Binding<Bool>) -> some View {
self.modifier(ToastModifier.init(show: show, toastView: toastView))
}
}
We will use this newly created modifier by attaching it to another view, just like any other view modifier.
.toast(toastView: Toast(dataModel: ToastDataModel(title: "Success", image: "checkmark"), show: $showToast), show: $showToast)
View code:
struct DevTechieToastExample: View {
@State private var showToast = false
@State private var emailSent = false
var body: some View {
NavigationStack {
VStack {
if emailSent {
Text("Email Sent")
.font(.largeTitle)
} else {
VStack(alignment: .leading) {
Text("Email")
.bold()
TextField("Email Address", text: .constant("devtechie@devtechie.com"))
.textFieldStyle(.roundedBorder)
Text("Subject")
.bold()
TextField("Email Subject", text: .constant("Your video courses are awesome!"))
.textFieldStyle(.roundedBorder)
Text("Body")
.bold()
TextEditor(text: .constant("Hi there, \n Thanks for your video courses, they are awesome! Keep up the good work."))
HStack {
Button("Send") {
withAnimation {
emailSent.toggle()
showToast.toggle()
}
}
.buttonStyle(.borderedProminent)
Button("Cancel") {
}
.buttonStyle(.bordered)
}
}
.padding()
}
}
.navigationTitle("DevTechie")
}
.toast(toastView: Toast(dataModel: ToastDataModel(title: "Success", image: "checkmark"), show: $showToast), show: $showToast)
}
}
Complete code should look like this
struct DevTechieToastExample: View {
@State private var showToast = false
@State private var emailSent = false
var body: some View {
NavigationStack {
VStack {
if emailSent {
Text("Email Sent")
.font(.largeTitle)
} else {
VStack(alignment: .leading) {
Text("Email")
.bold()
TextField("Email Address", text: .constant("devtechie@devtechie.com"))
.textFieldStyle(.roundedBorder)
Text("Subject")
.bold()
TextField("Email Subject", text: .constant("Your video courses are awesome!"))
.textFieldStyle(.roundedBorder)
Text("Body")
.bold()
TextEditor(text: .constant("Hi there, \n Thanks for your video courses, they are awesome! Keep up the good work."))
HStack {
Button("Send") {
withAnimation {
emailSent.toggle()
showToast.toggle()
}
}
.buttonStyle(.borderedProminent)
Button("Cancel") {
}
.buttonStyle(.bordered)
}
}
.padding()
}
}
.navigationTitle("DevTechie")
}
.toast(toastView: Toast(dataModel: ToastDataModel(title: "Success", image: "checkmark"), show: $showToast), show: $showToast)
}
}struct ToastModifier : ViewModifier {
@Binding var show: Bool
let toastView: Toast
func body(content: Content) -> some View {
ZStack {
content
if show {
toastView
}
}
}
}extension View {
func toast(toastView: Toast, show: Binding<Bool>) -> some View {
self.modifier(ToastModifier.init(show: show, toastView: toastView))
}
}struct ToastDataModel {
var title:String
var image:String
}struct Toast: View {
let dataModel: ToastDataModel
@Binding var show: Bool
var body: some View {
VStack {
Spacer()
HStack {
Image(systemName: dataModel.image)
Text(dataModel.title)
}.font(.headline)
.foregroundColor(.primary)
.padding([.top,.bottom],20)
.padding([.leading,.trailing],40)
.background(Color(UIColor.secondarySystemBackground))
.clipShape(Capsule())
}
.frame(width: UIScreen.main.bounds.width / 1.25)
.transition(AnyTransition.move(edge: .bottom).combined(with: .opacity))
.onTapGesture {
withAnimation {
self.show = false
}
}.onAppear(perform: {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
self.show = false
}
}
})
}
}