Dans un UIViewController
normal dans Swift, j'utilise ce code pour envoyer un 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()
}
Comment puis-je obtenir la même chose dans SwiftUI?
Dois-je utiliser UIViewControllerRepresentable
?
Comme vous l'avez mentionné, vous devez porter le composant sur SwiftUI
via UIViewControllerRepresentable
.
Voici une implémentation simple:
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>) {
}
}
tilisation:
struct ContentView: View {
@State var result: Result<MFMailComposeResult, Error>? = nil
@State var isShowingMailView = false
var body: some View {
ZStack {
VStack {
Button(action: {
self.isShowingMailView.toggle()
}) {
Text("Show mail view")
}
if result != nil {
Text("Result: \(String(describing: result))")
.lineLimit(nil)
}
}
if (isShowingMailView) {
mailView()
.transition(.move(Edge: .bottom))
.animation(.default)
}
}
}
private func mailView() -> some View {
MFMailComposeViewController.canSendMail() ?
AnyView(MailView(isShowing: $isShowingMailView, result: $result)) :
AnyView(Text("Can't send emails from this device"))
}
}
Notes:
J'utilise un ZStack
pour le montrer, car le comportement Modal
était assez incohérent.
(Testé sur iPhone 7 Plus sous iOS 13 - fonctionne comme un charme)
mis à jour pour Xcode 11 beta 5
@ La réponse de Matteo est bonne mais elle doit utiliser la variable d'environnement de présentation. Je l'ai mis à jour ici et il répond à toutes les préoccupations dans les commentaires.
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()
}
}
Les réponses sont correctes Hobbes the Tige & Matteo
Dans les commentaires, si vous devez afficher une alerte si aucun e-mail n'est configuré sur le bouton ou appuyez sur le geste
@State var isShowingMailView = false
@State var alertNoMail = false
HStack {
Image(systemName: "envelope.circle").imageScale(.large)
Text("Contact")
}.onTapGesture {
MFMailComposeViewController.canSendMail() ? self.isShowingMailView.toggle() : self.alertNoMail.toggle()
}
// .disabled(!MFMailComposeViewController.canSendMail())
.sheet(isPresented: $isShowingMailView) {
MailView(result: self.$result)
}
.alert(isPresented: self.$alertNoMail) {
Alert(title: Text("NO MAIL SETUP"))
}
Pour pré-remplir To, Body ...
func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
let vc = MFMailComposeViewController()
vc.setToRecipients(["[email protected]"])
vc.setMessageBody("<p>You're so awesome!</p>", isHTML: true)
vc.mailComposeDelegate = context.coordinator
return vc
}