How to Build Toast Message in SwiftUI?

DevTechie Inc
May 11, 2023

Today we will build a custom modifier to present toast message in SwiftUI. Out final product will look like this.

Toast View Modifier

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
                }
            }
        })
    }
}

View Modifier

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))
    }
}

Use case

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
                }
            }
        })
    }
}