SwiftUI: Send email

2020-02-10 06:58发布

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?

3条回答
我只想做你的唯一
2楼-- · 2020-02-10 07:21

Answers are correct Hobbes the Tige & Matteo

From the comments, if you need to show an alert if no email is set up on the button or tap gesture

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

To pre-populate To, Body ...

func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
        let vc = MFMailComposeViewController()
        vc.setToRecipients(["your@mail.com"])
        vc.setMessageBody("<p>You're so awesome!</p>", isHTML: true)
        vc.mailComposeDelegate = context.coordinator
        return vc
    }
查看更多
聊天终结者
3楼-- · 2020-02-10 07:29

@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()
    }
}
查看更多
▲ chillily
4楼-- · 2020-02-10 07:42

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 {
        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:

I'm using a ZStack to show it, as the Modal behaviour was quite inconsistent.

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

Updated for Xcode 11 beta 5

查看更多
登录 后发表回答