In a normal UIViewController
in Swift, I use this code to send a mail.
let mailComposeViewController = configuredMailComposeViewController() mailComposeViewController.navigationItem.leftBarButtonItem?.style = .plain mailComposeViewController.navigationItem.rightBarButtonItem?.style = .plain mailComposeViewController.navigationBar.tintColor = UIColor.white if MFMailComposeViewController.canSendMail() { self.present(mailComposeViewController, animated: true, completion: nil) } else { self.showSendMailErrorAlert() }
How can I achieve the same in SwiftUI?
Do I need to use UIViewControllerRepresentable
?
SwiftUI helps you build great-looking apps across all Apple platforms with the power of Swift — and surprisingly little code. You can bring even better experiences to everyone, on any Apple device, using just one set of tools and APIs.
Show activity on this post. let email = "[email protected]" let url = URL(string: "mailto:\(email)") UIApplication. shared. openURL(url!)
iOS development never had a framework with “Cocoa” in the name but was still officially called “Cocoa Touch”. It's not as though the NeXTStep derived Objective-C classes are going to be wholly replaced any time soon. Even excluding Foundation, SwiftUI replaces only some Objective-C user-interface classes.
@Matteo's answer is good but it needs to use the presentation environment variable. I have updated it here and it addresses all of the concerns in the comments.
import SwiftUI import UIKit import MessageUI struct MailView: UIViewControllerRepresentable { @Environment(\.presentationMode) var presentation @Binding var result: Result<MFMailComposeResult, Error>? class Coordinator: NSObject, MFMailComposeViewControllerDelegate { @Binding var presentation: PresentationMode @Binding var result: Result<MFMailComposeResult, Error>? init(presentation: Binding<PresentationMode>, result: Binding<Result<MFMailComposeResult, Error>?>) { _presentation = presentation _result = result } func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { defer { $presentation.wrappedValue.dismiss() } guard error == nil else { self.result = .failure(error!) return } self.result = .success(result) } } func makeCoordinator() -> Coordinator { return Coordinator(presentation: presentation, 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:
import SwiftUI import MessageUI struct ContentView: View { @State var result: Result<MFMailComposeResult, Error>? = nil @State var isShowingMailView = false var body: some View { Button(action: { self.isShowingMailView.toggle() }) { Text("Tap Me") } .disabled(!MFMailComposeViewController.canSendMail()) .sheet(isPresented: $isShowingMailView) { MailView(result: self.$result) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With