My app needs to send iMessages with an attached data file. The view comes up modally and the user needs to press the send button. When the button is tapped the error occurs and the attachment and message is not delivered.
I have tried three implementations of UIViewControllerRepresentable for MFMessageComposeViewController that have worked for iOS 15 and 16, but not 17. If I make the simplest app to show the problem, it will reliably fail on all iOS versions tested.
If I remove the attachment, the message gets sent. In contrast, the equivalent UIViewControllerRepresentable for email works perfectly every time. After struggling for weeks to get it to work, I am beginning to believe this is a timing error in iOS. I have even tried unsuccessfully to include dispatch queue delays.
Has anybody else written a swiftUI based app that can reliably attach a file to an iMessage send?
UIViewControllerRepresentable:
import SwiftUI import MessageUI import UniformTypeIdentifiers struct AttachmentData: Codable { var data:Data var mimeType:UTType var fileName:String } struct MessageView: UIViewControllerRepresentable { @Environment(\.presentationMode) var presentation @Binding var recipients:[String] @Binding var body: String @Binding var attachments:[AttachmentData] @Binding var result: Result<MessageComposeResult, Error>? func makeUIViewController(context: Context) -> MFMessageComposeViewController { let vc = MFMessageComposeViewController() print("canSendAttachments = \(MFMessageComposeViewController.canSendAttachments())") vc.recipients = recipients vc.body = body vc.messageComposeDelegate = context.coordinator for attachment in attachments { vc.addAttachmentData(attachment.data, typeIdentifier: attachment.mimeType.identifier, filename: attachment.fileName) } return vc } func updateUIViewController(_ uiViewController: MFMessageComposeViewController, context: Context) { } func makeCoordinator() -> Coordinator { return Coordinator(presentation: presentation, result: $result) } class Coordinator: NSObject, MFMessageComposeViewControllerDelegate { @Binding var presentation: PresentationMode @Binding var result: Result<MessageComposeResult, Error>? init(presentation: Binding<PresentationMode>, result: Binding<Result<MessageComposeResult, Error>?>) { _presentation = presentation _result = result } func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { defer { $presentation.wrappedValue.dismiss() } switch result { case .cancelled: print("Message cancelled") self.result = .success(result) case .sent: print("Message sent") self.result = .success(result) case .failed: print("Failed to send") self.result = .success(result) @unknown default: fatalError() } } } }
SwiftUI interface:
import SwiftUI import MessageUI struct ContentView: View { @State private var isShowingMessages = false @State private var recipients = ["4085551212"] @State private var message = "Hello from California" @State private var attachment = [AttachmentData(data: Data("it is not zip format, however iMessage won't allow the recipient to open it if extension is not a well-known extension, like .zip".utf8), mimeType: .zip, fileName: "test1.zip")] @State var result: Result<MessageComposeResult, Error>? = nil var body: some View { VStack { Button { isShowingMessages.toggle() } label: { Text("Show Messages") } .sheet(isPresented: $isShowingMessages) { MessageView(recipients: $recipients, body: $message, attachments: $attachment, result: $result) } .onChange(of: isShowingMessages) { newValue in if !isShowingMessages { switch result { case .success(let type): switch type { case .cancelled: print("canceled") break case .sent: print("sent") default: break } default: break } } } } } } #Preview { ContentView() }