Swiftui: Send Email

sending email with SwiftUI

You can use UIViewControllerRepresentable

MailComposeViewController

struct MailComposeViewController: UIViewControllerRepresentable {

var toRecipients: [String]
var mailBody: String

var didFinish: ()->()

func makeCoordinator() -> Coordinator {
return Coordinator(self)
}

func makeUIViewController(context: UIViewControllerRepresentableContext<MailComposeViewController>) -> MFMailComposeViewController {

let mail = MFMailComposeViewController()
mail.mailComposeDelegate = context.coordinator
mail.setToRecipients(self.toRecipients)
mail.setMessageBody(self.mailBody, isHTML: true)

return mail
}

final class Coordinator: NSObject, MFMailComposeViewControllerDelegate {

var parent: MailComposeViewController

init(_ mailController: MailComposeViewController) {
self.parent = mailController
}

func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
parent.didFinish()
controller.dismiss(animated: true)
}
}

func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: UIViewControllerRepresentableContext<MailComposeViewController>) {

}
}

Usage:

struct MailView: View {
@State private var showingMail = false

var body: some View {
VStack {
Button("Open Mail") {
self.showingMail.toggle()
}
}
.sheet(isPresented: $showingMail) {
MailComposeViewController(toRecipients: ["test@gmail.com"], mailBody: "Here is mail body") {
// Did finish action
}
}
}
}

Possible another solution. You can create one singleton class and present MFMailComposeViewController on the root controller. You can modify function as per your requirement. Like this

class MailComposeViewController: UIViewController, MFMailComposeViewControllerDelegate {

static let shared = MailComposeViewController()

func sendEmail() {
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setToRecipients(["you@yoursite.com"])
mail.setMessageBody("<p>You're so awesome!</p>", isHTML: true)
UIApplication.shared.windows.first?.rootViewController?.present(mail, animated: true)
} else {
// show failure alert
}
}

func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true)
}
}

Usage:

Button(action: {
MailComposeViewController.shared.sendEmail()
}, label: {
Text("Send")
})

SwiftUI: Send email

As you mentioned, you need to port the component to SwiftUI via UIViewControllerRepresentable.

Here's a simple implementation:

struct MailView: UIViewControllerRepresentable {

@Binding var isShowing: Bool
@Binding var result: Result<MFMailComposeResult, Error>?

class Coordinator: NSObject, MFMailComposeViewControllerDelegate {

@Binding var isShowing: Bool
@Binding var result: Result<MFMailComposeResult, Error>?

init(isShowing: Binding<Bool>,
result: Binding<Result<MFMailComposeResult, Error>?>) {
_isShowing = isShowing
_result = result
}

func mailComposeController(_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?) {
defer {
isShowing = false
}
guard error == nil else {
self.result = .failure(error!)
return
}
self.result = .success(result)
}
}

func makeCoordinator() -> Coordinator {
return Coordinator(isShowing: $isShowing,
result: $result)
}

func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
let vc = MFMailComposeViewController()
vc.mailComposeDelegate = context.coordinator
return vc
}

func updateUIViewController(_ uiViewController: MFMailComposeViewController,
context: UIViewControllerRepresentableContext<MailView>) {

}
}

Usage:

struct ContentView: View {

@State var result: Result<MFMailComposeResult, Error>? = nil
@State var isShowingMailView = false

var body: some View {

VStack {
if MFMailComposeViewController.canSendMail() {
Button("Show mail view") {
self.isShowingMailView.toggle()
}
} else {
Text("Can't send emails from this device")
}
if result != nil {
Text("Result: \(String(describing: result))")
.lineLimit(nil)
}
}
.sheet(isPresented: $isShowingMailView) {
MailView(isShowing: self.$isShowingMailView, result: self.$result)
}

}

}

(Tested on iPhone 7 Plus running iOS 13 - works like a charm)

Updated for Xcode 11.4

SwiftUI: Send email using MFMailComposeViewController

Building up on the code snippet shared in my original question:

Based on the answer from @Arjun this is my current workaround to account for the edge case that someone might have deleted the Apple Mail app and is using another email app:

Button(action: {
if MailComposeViewController.shared.thisIsTest() {
MailComposeViewController.shared.sendEmail()
} else {
openURL(URL(string: "mailto:someone@example.com?subject=This%20is%20the%20subject")!)
}
}, label: {
Text("Send")
})

It opens the in-app sheet as long as the user has set up apple mail and otherwise switches to any other email app using a mailto: link.

Send email using Default email provider - SwiftUI

iOS 14.0+ | Swift 5.3

On iOS 14.0; Apple allows iOS users to choose a default mail app in Settings

Users can choose their default email provider in settings, and all you have to do to compose an email would be:

let mailTo = "mailto:nemecek@support.com".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) 
let mailtoUrl = URL(string: mailto!)!
if UIApplication.shared.canOpenURL(mailtoUrl) {
UIApplication.shared.open(mailtoUrl, options: [:])
}

It needs to be percent-encoded with the addingPercentEncoding and then we would create an URL from this and open it with UIApplication.shared.open.

We can also use standard URL parameters to customize the mailto url. First parameter is denoted with ? and then others are chained with &.

We can add a subject and body like this:

let mailTo = "mailto:nemecek@support.com?subject=Cool app feedback&body=Hello I have an issue...".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)


Related Topics



Leave a reply



Submit