MFMessageComposeViewController, SwiftUI, with attachment

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()
}
Answered by Engineer in 803589022

This may be due to you trying to share a string with attachment via SMS and not MMS (blue bubble). This should work if sending via MMS.

Rico

WWDR - DTS - Software Engineer

Accepted Answer

This may be due to you trying to share a string with attachment via SMS and not MMS (blue bubble). This should work if sending via MMS.

Rico

WWDR - DTS - Software Engineer

MFMessageComposeViewController, SwiftUI, with attachment
 
 
Q