0

I have a button that allows you to send an email with all the content on your app. I'm iterating thru all the data stored in a core data container, and creating a string that I then pass to the sheet presenting the ability to send email.

When I test it, the string always seems to be empty, and I can see an error: [PPT] Error creating the CFMessagePort needed to communicate with PPT.

I'm using the same mechanism I use to email each item on the list, which works like a charm.

Anyway, I've see a lot of posts about the error, but nothing that points to a solution.

Here's the code, maybe it's related to how I call the .sheet? What am I missing? What's that error even try to tell me?

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(entity: Jot.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Jot.date, ascending: false)])
    var jots: FetchedResults<Jot>

    @State private var sheetbackupJotMail = false
    //for sending mail
    @State var result: Result<MFMailComposeResult, Error>? = nil
    @State var isShowingMailView = false
    @State private var emailText: String = ""

    var body: some View {
    
         NavigationView {
            List (jots) { jot in
                Text(jot.text!)
            }
         }
         .toolbar {
             // toolbar button that send the message with all content
            ToolbarItem(placement: .navigation) {
                if MFMailComposeViewController.canSendMail() {
                    Button(action: {
                        sheetbackupJotMail.toggle()
                    }) {
                        Label("Back up all jots", systemImage: "arrow.up.square").foregroundColor(.secondary)
                    }
                    // sheet for backing up email
                    .sheet(isPresented: $sheetbackupJotMail) {
                        MailView(result: $result) { composer in
                            emailText = ""
                            for jot in jots {
                                emailText = emailText + jot.dateText! + "\n" + jot.text! + "\n\n"
                            }
                            print(">>>: " + emailText) //<-- this is always empty, however if I move to the button before the toggle(), I get the right text
                            // emailing all
                            composer.setSubject("Jot Backup")
                            composer.setMessageBody(emailText, isHTML: false)
                        }
                    }
                }
            }
        }
    }

}


// mail view

import SwiftUI
import UIKit
import MessageUI


public struct MailView: UIViewControllerRepresentable {

    @Environment(\.presentationMode) var presentation
    @Binding var result: Result<MFMailComposeResult, Error>?
    public var configure: ((MFMailComposeViewController) -> Void)?

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

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

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

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

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

    }
}

Also moving the creation of the text here caused the text I need to mail to be ok, but the error continues to be: PPT] Error creating the CFMessagePort needed to communicate with PPT.

Button(action: {
     emailText = ""
     for jot in jots {
            emailText = emailText  + jot.dateText! + "\n" + jot.text! + "\n\n"
     }
     print(">>>: " + emailText)
     sheetbackupJotMail.toggle()
}) {
     Label("Back up all jots", systemImage: "arrow.up.square").foregroundColor(.secondary)
}
Aleph
  • 465
  • 2
  • 12

1 Answers1

0

The sheet content is computed only once on creation time, so all later changes in dependencies are not re-injected, so you have to put everything dependent by binding inside MailView and do composing there.

I.e. your sheet should look like (sketch)

.sheet(isPresented: $sheetbackupJotMail) {
    MailView(result: $result, emailText: $emailText, jots: $jots)
}

*Many dependencies are missing, so it is possible to provide testable solution, but the idea should be clear. See also https://stackoverflow.com/a/64554083/12299030 - it demos solution for similar issue.

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thank you, I did try to compose the text *before* calling the sheet, and while the text it's showing on the debug window, it is not passed to the sheet. I'll try changing the way the sheet is called... – Aleph Jul 24 '22 at 15:09
  • And dependencies? All the code that is needed is there. If you need to create a view so you can test, sure. I'll create a view. Please see edit. – Aleph Jul 24 '22 at 15:11